1           U 
C:\USER\NEWS\29\NEWS29D.DFV                                         POSTSCR @ c+  Hardwarenahes Programmieren
Teil1: Eine Rundschau
Franz Fiala, NT, TGM
Wenn man sich so die Filme der 20er Jahre ansieht, Autofahrer im Mechaniker-Anzug und nicht nur zur Kenntlichmachung ihrer Zugehrigkeit zu dieser Zunft, sonder wirklich zum Gebrauch. Da mute so mancher, der Auto fahren, und auch ein Ziel dabei erreichen wollte, mehr ber das Auto wissen als heutzutage. 
Manche Beitrge in Komputerzeitschriften, auch in der unseren zeigen Parallelitten zum Automobil. Wir knnen ja darber streiten in welcher Phase des Autobaues wir uns jetzt mit unseren Rechnern befinden aber weit weg von der (Komputer)Steinzeit sind wir noch nicht. 
Ich wei schon, da es blich ist, 'Computer' mit 'C' und nicht mit 'K' beginnen zu lassen aber durch etwas soll sich ja dieser Beitrag von anderen gleichartigen unterscheiden drfen. (Ab wann schreibt sich 'Friseur' als 'Frisr'? Wenn die Frisre damit beginnen? Der Komputer ist bekannt genug, um kein Modewort mehr zu sein. Beginnen wir damit!) ber die Schreibweise in diversen einschlgigen Artikeln, auch den unseren, knnte man sich an anderer Stelle ausfhrlicher unterhalten. 
Doch zurck zur Frage, warum Steinzeit? 
Rckblick
MSDOS begann mit CP/M
Das erste weit verbreitete und daher schon fast 'persnliche Komputersystem', CP/M (Control Program for Microprozessors) der Firma Digital Research war ein Vorgnger unseres MSDOS. (Heute wrde es vielleicht DRCPM heien). Da unser heutiges System auf dem vorigen aufbaut, ist es gut diese Wurzeln zu kennen. Was waren eigentlich die Unterschiede der beiden Systeme? Im Grunde nur der, da sie auf verschiedenen CPUs aufbauten. CP/M baute auf 8085 oder Z80-Systemen auf, MSDOS, wie wir wissen auf 80x86-Systemen. Details, wie die Umkehrung der Befehlsrichtung als Verneigung vor dem Benutzer und die Bercksichtigung von Datum und Uhrzeit sind nur kosmetische nderungen, vergleicht man die Versionen CP/M 3.3 mit MSDOS 1.0. 
Manche Z80-CPUs der letzten Generation kamen mit ihrer Leistungsfhigkeit ganz schn an die 8088-CPU heran, soda sich viele Leute, gewhnt an CP/M, eigentlich fragten, wo denn der Vorteil der neuen Hardware sei. Die Hardware war es eigentlich auch nicht, sieht man einmal von dem damals gigantischen Speicher von, zumindest theoretisch, 1MB ab, der ja beim Standard-IBM-PC auch nur mit 64k-Byte bestckt war. Es waren die Programme, die erst durch das 'um-das-Betriebssystem-herum-Programmieren' und das 'um-das-BIOS-herum-Programmieren' entstehen konnen. Das war nur mglich, da alle Gerte identischen Hardwareaufbau hatten. 
Die Programme waren von bisher unbekannter Leistungsfhigkeit. Das Betriebssystem sowohl von CP/M als auch das von MSDOS ermglichte zwar eine Dateiverwaltung, hatte aber nur bescheidene Mglichkeiten zur Bildgestaltung. Bei verschiedenartiger, darunterliegender Hardware, wie bei CP/M war dieses 'Herumprogrammieren' sinnlos und nur auf speziellen Maschinen einsetzbar. Jetzt, da alle Hardware identisch wurde, kamen schnelle Programme auf eigentlich langsamer Hardware. 
Das Betriebssystem hat die Aufgabe, gleichbleibende Benutzeroberflche zur Verfgung zu stellen, unabhngig von der verwendeten Hardware. Was beim CP/M-Betriebssystem ohne groe Probleme gelang, nmlich auch sehr verschiedene Hardware unter einen (Betriebssystem-)Hut zu bringen, geht zwar auch unter MSDOS, doch angesichts der gleichen darunterliegenden Hardware programmierten die Programmierer, den Wnschen der Anwender nach Schnelligkeit und Bedienungskomfort entsprechend, einfach am Betriebssystem vorbei, zuerst mit dem BIOS und, wenn es noch nicht reichte, hinein in die Hardware. Jetzt sehen wir auch, warum das IBMBIO.COM so klein ist, es is ja nicht viel anzupassen, bei identischen Bauformen, einfach nur Fehlerbehebung. 
Vorher gab es 'das CP/M' nicht im Geschft zu kaufen, jeder Komputererzeuger mute eine Rohversion kaufen und dann nach genauen Anweisungen an sein System, an sein BIOS anpassen. Es war viel hardwarenahes Programmieren erforderlich. Auch MSDOS,Ver.4 wurde derart vermarktet, nicht mit sehr groem Erfolg, man ist wieder am alten Weg angelangt. Im wesentlichen betrachtet das Betriebssystem MSDOS alle PCs, egal ob XT, AT oder 386er als dieselbe Maschine und nimmt eventuelle Anpassungen selbst vor. In der CP/M-Zeit wre das die Aufgabe der Komputerhersteller gewesen. Dem Enduser und dem Anwendugsprogrammierer blieb danach nur die Kommunikation mit dem Betriebssystem, wollte er sein Programm auch wirklich in allen CP/M-Implementierungen laufen sehen. Seine Kenntnisse beschrnkten sich auf die von CP/M. 
Wir sehen, das hardwarenahe Programmieren, so wie wir es heute kennen, ist ein Produkt des PC, da es mglich war, unterhalb des Betriebssystems anzusetzen, unter der Annahme, der Kufer wrde schon eine kompatible Maschine haben. Und die geschriebenen Programme wren ber das Betriebssystem allein auch gar nicht realisierbar gewesen (auch wenn die Funktionen ausrgereicht htten), die Hardware war zu langsam. 
Zurck in die Zukunft?
Die Aufrufe ber das Betriebssystem, speziell die fr die Benutzerkommunikation, gewinnen erst dann wieder an Bedeutung, wenn die Rechner ausreichend schnell sind, den Betriebssystem-overhead ohne Verlust an Bedienungskomfort zu tragen. Es ist die Zeit der grafischen Benutzeroberflchen, in der wir uns eigentlich schon befinden und wenn wir die aktuellen Versionen der Betriebssysteme WINDOWS und OS/2 betrachten, sehen wir, da es bereits Realitt ist, was man auf Grund der Hardwareentwicklung schon absehen konnte: Nicht nur, da die neuen Betriebssysteme eine logische Folge der hier dargelegten Gesichtspunkte sind, sie sind ohne grere Probleme auf die verschiedensten Systeme portierbar. Dafr sorgt vor allem eines: Sie sind nicht mehr in Assembler geschrieben und somit von einer bestimmten CPU abhngig, sondern groteils in C, soda eine bertragung auf andere Hardwareumgebungen keine Schwierigkeit mehr darstellt. Beispiel: XWINDOWS(UNIX) und WINDOWS(MSDOS). 
Kehrt die Vielfalt der Hardware wieder ein? Wird es mglich sein, unter demselben WINDOWS gleiche Programme (nur mit geringem Aufwand neu compiliert) auf verschiedenen Hardwaresystemen zu starten? Wir werden zwar die Schwierigkeit haben, Programme in mehrere Versionen lagern zu mssen, dafr aber den Vorteil, andere CPUs verwenden zu knnen, wo wir heute auf die 80x86-Serie angewiesen sind. Vielleicht bedeutet das: 
1.	die Ablsung unserer derzeitigen Rechner. Sie haben ihre Aufgabe, der Trger einer Phase zu sein, in der die Hardwarekompatibilitt aus Geschwindigkeitsgrnden erforderlich war, erfllt und knnen leistungsfhigeren Rechnern Platz machen, ohne Legionen von Benutzern das Abenteuer des Umlernens auf eine neue Programmgeneration aufzubrden. 
2.	die Verwendbarkeit vieler verschiedener Hardwaretypen und nicht eine Hardware-Monokultur, so wie heute. 
Die Verwendung von 'Monokultur' fr die Hardware-Landschaft ist nicht ganz beilufig, denn unser neuester Feind, der Komputer-Virus, kann sich in seinen unangenehmsten Formen gerade deshalb so gut ausbreiten, weil er auf jedem System identische Verhltnisse in der Hardware vorfindet. Daher knnen Viren unter Beachtung der Hardwaregegebenheiten das Betriebssystem umgehen und haben Erfolg. Wenn hingegen sich ein Virus im Zuge des DOS-Interrupt 21h einnistet, ist er mit einiger Sicherheit durch Virusblocker erkennbar. 
Fassen wir zusammen: Die Langsamkeit der Hardware vor 10 Jahren begnstigte die hardwarenahe Programmierung im ungnstige Sinn, dort, wo sie nicht angebracht ist. Die schnellen Rechner von heute ermglichen einheitliche Benutzeroberflchen auf verschiedenen Rechnern. Das hardwarenahe Programmieren beschrnkt sich bei der groen Mehrheit der Programmierer auf die Kommunikation mit dem Betriebssystem. Bis es wirklich soweit ist, und vergessen wir nicht jene, die das Betriebssystem zu organisieren haben, mssen wir mit der bestehenden Hardware auskommen und sie genau kennenlernen. 
Begriffseingrenzung
Eine hier verwendete Begriffsbestimmung sollte uns auch helfen das 'hardwarenah' genauer einzugrenzen. Wir stellen die Schichten der Programmierung im Rechner dar und zeigen den bergang zur darunterliegenden Hardware: 
        ͻ                                  MSDOS      'Systemnahes Programmieren'                              ͹                                  BIOS       'Hardwarenahes Programmieren'                              ͹Software               'Hardwareprogrammierung'Ŀ   Register   Hardware                      Ĵ <--Elektrische Metechnik         Verdrahtung      Oszilloskop, Logikanalysator,...         zu anderen            Baugruppen           
Hardwareprogrammierung
Die unterste mit programmtechnischen Mitteln erreichbare Ebene sind die Register. Register sind Speicher mit Ein- und/oder Ausgngen nach auen, d.h. ein Register kann sowohl durch ein Programm geschrieben und/oder gelesen und durch externe elektrische Spannungswerte geschrieben und/oder gelesen werden, je nach Anforderung und Ausfhrung der entsprechenden Hardwarebausteine. 
Hardwareprogrammierung ist einerseits Programmieren in den Registern der Peripherie und anderseits direkter Speicherzugriff. Eine Programmierart, die es, wie wir etwas spter sehen werden, eigentlich gar nicht geben sollte, zumindest nicht bei einem Anwendungsprogrammierer. Es ist das unmittelbare Gegenstck zu einem abtastenden Meverfahren, wenn wir per Programm einen Registerinhalt abfragen. 
Genauso, wie das 'Herumstochern' in den Registern der Hardware, so ist auch das direkte Schreiben und Lesen im Hauptspeicher bereits eine Hardwareprogrammierung, denn dafr stehen uns normalerweise Funktionen des Betriebssystems oder des BIOS zur Verfgung. Das BIOS hat aber nur punktuelle Mglichkeiten zur Speicherverwaltung, das ist die Strke des Betriebssystems. Whrend es uns im Falle des Hauptspeichers einleuchtet, da wir nicht berall Daten ablegen knnen, ist es im Falle der IO-Register weniger gebruchlich davon abzusehen. 
Entwickler von Hardware, die zumeist auch gleichzeitig die Entwickler der dazugehrigen Treiber sind, mssen das aber immer tun. Programmiert jemand auf diese Art 'in der Hardware herum', dann setzt er hardwarekompatible Gerte voraus. 
Fr die Hardwareprogrammierung bentigen wir genaue Kenntnisse ber die Adressen der Register, ihre Funktion und die Funktion eines jeden Bit in ihnen. Wir werden eine Liste der Register und ihrer Inhalte erstellen. 
Hardwarenahe Programmierung
Das BIOS (Basic-Input-Output-System) ist ein in einem Festwertspeicher (ROM) residierendes Programm, das mehrere Aufgaben hat: 
1.	Systemtest (POST)
2.	Initialisierung aller Interrupts, insbesodere Nummer 0..7(CPU) und 8..15(Hardware-Interrupts) 
3.	Initialisierung der sogenannten BIOS-Interrupts (16-31), die das hardwarenahe Programmieren ermglichen. 
4.	Initialisierung aller BIOS-Variablen
5.	Setzen der Systemzeit aus dem CMOS-RAM
6.	Aufruf aller spter eingebauten BIOS-Erweiterungen durch selbstndiges Suchen nach BIOS-Erweiterungen.
7.	Booten des Rechners durch Suchen einer Diskette im Laufwerk A, Lesen des Bootsektors und Starten des Bootprogramms auf Offset 0. Wiederholung des Vorgangs fr eine etwa vorhandene Festplatte. Starten eines etwa eingebauten BASIC-Interpreters. 
Hardwarenahe Programmierung bedeutet Kommunikation der Programme mit den BIOS-Interrupts. Jede Kommunikation mit der Hardware beschrnkt sich auf das Aufrufen von BIOS-Funktionen, die ihrerseits mit den Registern der Peripherie kommunizieren. Vorteil: Kenntnisse der Hardwarebausteine nicht erforderlich, nur die Tabelle der BIOS-Funktionen mu gekannt werden. Die meisten Rechner verschiedener Hersteller haben verschiedenartigen Hardwareaufbau, sind aber, ber die BIOS-Funktionen programmiert, kompatibel. ('BIOS-Kompatibilitt'). Zum Glck besteht der Unterschied in der Hardware meist in Bereichen, der nicht einer strengen Kompatibilittsforderung unterliegt (hauptschlich Systemkonfiguration in den verschiedenartigen Chipstzen). 
Wie wir noch sehen werden reicht diese Art der Kompatibilitt bei unserer heutigen Rechner-Monokultur nicht immer aus. Im Prinzip sind aber alle unsere Rechner BIOS-kompatibel, die Hardware ist wegen der unterschiedlichen Chip-Stze immer verschieden. Allerdings beziehen sich die Unterschiede auf einen Bereich, der normalerweise von den Programmierern unbehelligt bleibt: die Einstellung der Gerteeigenschaften auf Grund der vorhandenen Hardware. Aus der Ebene des BIOS ist der Hauptspeicher praktisch noch 'vogelfrei', kann also praktisch frei verwendet werden aber wer programmiert schon mit dem BIOS allein!
Das BIOS kompensiert in einigen Fllen auch Versumnisse der Entwickler der Peripherie-ICs. In vielen Fllen wurde es verabsumt, die Register rcklesbar zu machen, soda ein Anwenderprogramm den aktuellen Zustand der Peripherie nicht feststellen kann. Hier sieht man deutlich die Gefahr, die besteht, wenn man das BIOS umgeht, und die Registerinhalte verndert, da das BIOS einen anderen Zustand protokolliert hat.
Systemnahe Programmierung
MSDOS ist unser Betriebssystem, welches die Aufgabe hat eine Nutzung der vorhandenen Resourcen, wie Massenspeicher, Ein-/Ausgabe-Kanle durch eine eigene Kommandozeilensprache zugnglich und bedienbar zu machen. Schlielich aber auch (und fr Programmierer ist das der wesenlichere Teil) ein Zurverfgungstellen primitiver Routinen, die es Programmen erbrigt, in den physikalischen Dimensionen Spur-Sektor-Seite zu denken. Stattdessen spricht er die Datei ebenso unter einem Dateinamen an, wie man es auch auf der Benutzerebene gewhnt ist. 
Systemnahe Programmierung bedeutet Kommunikation der Programme mit den Funktionen des Betriebssystems. Dieser Programmierstil entfernt sich noch weiter von der eigentlichen Hardware, da die Betriebssystemfunktionen ihrerseits BIOS-Funktionen rufen. Auch hier knnen wir uns Rechner vorstellen, die auf der Ebene des Betriebssystems kompatibel sind und verschiedenartiges BIOS oder Hardware besitzen. Diese Rechner sind aber praktisch vom Markt verschwunden. Das Betriebssystem bernimmt die Aufgabe der Speicherverwaltung. Normalerweise darf kein Speicher ohne ausdrckliche Genehmigung des Betriebssystems verwendet werden. 
Leitfaden des hardwarenahen Programmierens
Ein frei bersetzter Satz aus Peter Nortons Programmierhandbuch fr den IBM-PC lautet etwa: 'Eine bestimmte Programmieraufgabe ist auf dem hchstmglichen (Software-)Niveau zu lsen.' Nur Ausnahmssituationen (z.B. Lernen) rechtfertigen eine andere Vorgangsweise. Ein anwenderorientiertes Problem rechtfertigt keinen Assemblereinsatz sondern eine moderene Hochsprache. Je hher wir das Niveau ansetzen, desto grer ist die zu erwartende Hilfe durch das benutzte System. Bei der Bildausgabe verwende man den BIOS-Interrupt 10h statt im Grafikbereich 'herumzupixeln' und rate den Anwednern eher einen schnelleren Rechner als Klimmzge durch Hardwareprogrammierung zu unternehmen. Dieses Motto soll uns bei unseren Programmierversuchen leiten. 
Wann hardwarenah programmieren?
Der Anwendungsprogrammierer
1.	Die Bibliothek der verwendeten Sprache bietet keine entsprechenden Funktionen, daher mu das Betriebsystem verwendet werden. 
2.	Das Betriebssystem ist zu langsam, fehlerhaft oder unvollstndig, daher wird das BIOS verwendet
3.	Das BIOS ist zu langsam, fehlerhaft oder unvollstndig, daher werden die Hardwareregister verwendet. 
Der Hardwareentwickler
Immer, denn fr die Einbindung neuer Hardware in ein bestehendes System oder gar fr die Entwicklung eines selbstndigen System, etwa eines Mikro-Kontrollers, gibt es nur diesen Weg. 
So, wie wir die hardwarenahe Programmierung durch Nutzung vorhandener Systembestandteile vermeiden sollten, so genau sollten wir sie zu verfolgen in der Lage sein, wenn wir keine entsprechende Systemfunktion vorfinden sollten. 
Der vollkommene Kompiler
Dieser Kompiler und seine Bibliothek enthalten genau jene Funktionen, die man zur Benutzung des Rechners brauche. Nicht einfach Verlngerungen des Betriebssystems oder des BIOS oder Ein-/Ausgabebefehle, nein: Fr jede gewnschte Handlung gibt es eine passende Funktion, die Bibliothek nimmt die Anpassung an die Hardwaregegebeneheiten vor. 
Der unvollkommene Kompiler
Bei der ersten Kontaktaufnahme mit einem Kompiler schaut auch alles so aus. Je mehr man einsteigt, desto hufiger wird man gezwungen tiefer zu graben, denn nur die hufigsten Funktionen werden durch die Kompiler abgedeckt: Dateihandhabung, Grafik, Tastatureingabe, Speicherverwaltung. In allen Fllen wo wir Funktionen vermissen, wird ein Griff in die Systemlade fllig, das systemnahe Programmieren beginnt. 
Das vollkommene Betriebssystem
liest uns jeden Wunsch von den Augen ab, und kann jede Ein- Ausgabeeinheit bedienen. 
Das unvollkommene Betriebssystem
Vieles ist nicht vertreten (Bildaufbau), fehlende Befehle mssen durch hardwarenahes oder systemnahes Programmieren unter Zuhilfenahme des BIOS oder durch Kombination bestehender MSDOS-Funktionen hinzugefgt werden. Beispiele: Vertauschung der COM-Ports, Environment-Editor, Rekursives Lschen von Subdirectories usw. 
Das perfekte BIOS
Eigentlich sollte es fr Anwender hardwarenahes Programmieren gar nicht geben! Oder? Welchen Sinn hat denn das BIOS (Basic-Input-Output-System)? Wre das BIOS perfekt programmiert, brauchte man, zumindest am PC, diese Programmiertechnik nicht, denn fr jedes Anliegen an die Hardware sollte es eine geeignete BIOS-Funktion geben, die sie ausfhrt. Was uns bliebe, wre der geordnete Aufruf einer Reihe von BIOS-Funktionen oder, wie im PC, von BIOS-Interrupts. 
Wenn wir auch ein Ia-BIOS htten, das uns alle Hardwareprobleme abnimmt: wer konstruiert das BIOS? Da gengt es nicht ein paar Kenntnisse ber Hardware und ein paar ber Assembler zu haben, in diesen schwierigen Programmteilen liegt ein besonderes Ma an Verantwortung, da alle darberliegende Software darauf aufbaut. Bedenken wir, wie viele DOS-Versionen wir bereits gewechselt haben: alle liefen aber auch am BIOS des ltesten PC. Es ist daher von fundamentaler Bedeutung hier korrekt vorzugehen. 
Auerdem gewhrt uns das Experimentieren mit Hardwarezugriffen, zunchst einmal unabhngig von einer verwendeten Sprache, wichtige Einblicke in die Funktionsweise der Hardware, hnlich, wie es auch ein Oszilloskop bei der elektrischen Hardwareanalyse ermglicht. 
Das unvollkommene BIOS
Da das nicht immer geht, hat zumeist zwei Grnde: 
1.	Das BIOS ist nicht in allen Fllen vollstndig genug und in einigen Fllen auch zu wenig leistungsfhig, daher ist es immer wieder notwendig, diese Mngel auszugleichen. 
Diesen Fall erleben wir tglich beim Laden des MSDOS-Betriebssystems. Es gibt da zwei verborgene Dateien, eine kleine (IBMBIO.COM oder IO.SYS) und eine groe (IBMDOS.COM oder MSDOS.SYS). Die groe ist die Menge der MSDOS-Interrupts 20-2f, die kleine sind viele Ergnzungen, um Unzulnglichkeiten des BIOS auszubgeln. 
2.	Das BIOS ist in vielen Fllen nicht schnell genug, daher programmiert man in Eigenregie um das BIOS herum. 
In erster Linie sind hier der Timer und der Bildaufbau zu nennen. 
Das unvollstndige BIOS
Der Lautsprecher wird vom BIOS nicht untersttzt. Es gibt zwar eine Tonausgaberoutine, die an einer definieren Adresse steht, jedoch keinen Interrupt, der eine Parameterbeeinflussung zuliee. Das MSDOS beschftigt sich nicht weiter mit diesem Mangel, da fr allgemeine Broanwendungen der vorhandene 1-kHz-Pieps bei weitem ausreicht; ein Mangel aber, der imerhin Marktlcke genug war, um von ATARI und AMIGA ausgennutzt zu werden, denn man kann nur staunen, wie diese Gerte mit einem Lautsprecher umgehen knnen. Man kann diese Unzulnglichkeiten ausgleichen: Durch geeignete Hardwareprogrammierung und/oder durch zustzliche Hardware. 
Bei den Dienstleistungen des Tastatur-Interrupt ist es zum Beispiel nicht einfach mglich, eine bereits abgeholte Taste wieder zurckzustellen; d.h. es ist schon mglich: mit Hardwareprogrammierung. 
Das schlecht programmierte BIOS
Die serielle Schnittstelle ist berhaupt ein Stiefkind im PC. Es gibt nur einen Puffer fr ein einziges Zeichen, soda bei etwas hherer Geschwindigkeit Zeichenverluste an der Tagesordnung stehen. Der einzige Ausweg: Neuprogrammierung des betroffenen BIOS-Interrupt-14h (Hardwareprogrammierung).
Es gibt viele Hardwaredetails, Konfigurationdetails, die nicht ber Interrupts erreichbar sind, und man daher auf geziele Programmierung der Hardware angewiesen ist. Beispielsweise ist die Feststellung der CPU-Type (8086/286/386/486) oder der Koprozessor-Type (8087/80287/80387) eine spitzfindige Angelegenheit, offenbar, weil man beim Entwurf des AT-BIOS an diese Entwicklungsmglichkeit verga und daher in jedem allgemein-verwendbaren Programm diese zustzlichen Routinen einbauen mu, sofern diese Unterschiede bedeutsam sind. 
Das langsame BIOS
Ein typisches Beispiel ist der Timer mit seinen 53ms Periodendauer. Ein durchaus gnstiger Wert, wenn man an Hintergrundaufgaben denkt; diese Aufgaben werden ausreichend oft aktiviert, um quasi-unbemerkt vom Benutzer ablaufen zu knnen, auch werden sie ausrechend selten aktiviert, um durch besonders rechenintensive Hintergrundprogramme und den gleichbleibenden Overhead nicht eine erhebliche Verlangsamung der Rechnergeschwindigkeit hervorzurufen. 
Fr viele Aufgaben ist aber sowohl die Lnge mit 53ms ein zu 'krummer' Wert als auch die Dauer einfach zu gro. In diesem Bereich fallen daher viele, besonders zeitkritische Hardware-Programmieraufgaben an. 
Ein hufiges Beispiel ist die Bildschirmprogrammierung, insbesondere bei Spielen. In gewisser Weise ist ja diese Entwicklung ein Rckschritt, sogar gegenber dem MSDOS-Vorgnger CP/M. Wie bedeutend auch die einheitliche Hardware auf allen PCs auch war, sie brachte ein wichtiges Wort ins Spiel: die Kompatibilitt. Nicht eine Adresse durfte anders belegt sein, damit etwa der Flugsimulator von Microsoft einwandfrei lief. Ein mitlerweile gelstes Problem, denn inkompatible Gerte haben keine groe Chance, am Markt zu bestehen. Was IBM-kompatibel ist wird nicht mehr bei IBM, sondern lange schon in Taiwan festgelegt. Inkompatible Gerte (zum Industriestandard) verschwanden vllig vom Markt. Die eigentliche Aufgabe des Betriebssystems ging dabei verloren. berspitzt knnte man sagen, MSDOS wre eine Erweiterung des BIOS, da ohnehin praktisch nur an PCs gefahren wird. 
Es gibt sicher noch weitere wichtige Mngel, die wir durch Hardwareprogrammierung ausgleichen werden mssen. 
Quellen des PC
Die eigentliche Grundlage fr unser Wissen ber den PC, seine Registerbelegung sind: 
1.	Die Schaltungsdetails des IBM-PC im Technical-Reference-Manual 
2.	Die Schaltungsdetails des IBM-AT im Technical-Reference-Manual 
3.	Datenbltter der in 1. und 2. verwendeten, adressierbaren ICs. TGM-LIT-032
4.	Datenbltter, bzw. Bcher der CPUs 8088/86, 80286, 80386, 80486 ("Software Programming Manuals").
5.	Technisches Referenz-Handbuch der MSDOS, Ver. 3.3 und 5.0
Eine wesentliche Grundlage fr die Verbreitung unserer PCs sind die obigen Dokumente 1. und 2., die es den vielen PC-Kopisten erlaubt haben, mit verhltnismig wenig Aufwand, quasi-identische Versionen des Original-PC zu erstellen. 
Leider sind die Darstellungen der Schaltplne des PC fr das Verstndnis der Zusammenhnge wenig geeignet. Wir knnen aber eine fr den Unterrichtsbedarf von Koll. Winkler umgezeichnete Version benutzen, die wir unter TGM-LIT-038 auch unseren Lesern zur Verfgung stellen drfen. 
Der Umfang der angefhrten Quellen ist sehr gro und nur mit Mhe lesbar, beispielsweise beschrnkt sich das BIOS auf den Abdruck des Source-Kode, und es ist der Verdienst vieler Buchautoren, die die entsprechenden Interrupts ausfhrlich beschreiben. 
Leider exisitiert ber den PC und seine Software keine einheitliche 'Norm', vieles setzt sich durch gemeinsame Vereinbarung potenter Hersteller durch (Beispiel: LIM- XMS-Standard). Viele wichtige Details des Betriebssystems sind berhaupt nicht dokumentiert, unser Wissen beschrnkt sich auf Publikationen auerhalb der Hersteller. Speziell speicherresidente Programme oder Speicherbelegungsroutinen sind ohne diese Dokumentationen nicht nachvollziehbar. Wir benutzen fr diese Details die Dokumentation TGM-LIT-004 oder die Diskette TGM-DSK-140. 
Die zunehmende Integration und die Neuorientierung der Firmenpolitik bei IBM hat einen Schlustrich unter die freizgige Informationsweitergabe gezogen. Haben die Konstrukteure aus der Taiwan-Elektronik-Kche den ersten Nachbau-PC's noch IBM-nachempfundene Schaltungen beigelegt, wurde das bald eingestellt, soda wir noch immer mit den alten Darstellungen arbeiten mssen. 
Da aber alle Nachbauer auf Kompatibilitt bedacht sein mssen, knnen wir davon ausgehen, da die Bedeutung der Registerinhalte bei allen PC's trotz hherer Integration gleichbleibt. Schwieriger haben es da schon Hardware-Servicetechniker, die ohne eine genaue Schaltung nur auf Vermutungen ber diesen oder jenen 'Jumper' angewiesen sind. Da es auch ohne Schaltung gehen kann, zeigen einige Wenige, mit der notwendigen Portion Intuition und der zweiten, nicht minder wichtigen Portion Wissen ausgerstet sind. 
Unser heutiger, zwangsweise stark hardwarenaher Programmierstil ist durch die Vielzahl der Urspnge und Quellen gekennzeichnet, und es ist mhsam, sich durch das Labyrinth von nur aus der Entwicklung zu verstehenden Randbedingungen durchzukmpfen. 
Da es auch anders gehen kann, zeigt das Beispiel APPLE; allerdings nicht gewollt (in der Vergangenheit kmpften die Anwender von APPLE-IIe noch mit hnlichen Problemen, wie heute die PC-Anwender). Bei APPLE gibt der Erzeuger des Grundgerts den Ton an und stellt alle, fr Erweiterungen relevanten, Konstruktionsmerkmale Fremdentwicklern zur Verfgung. Es gibt eigene Vereinigungen fr Entwickler, soda diese Rechner eine grere Einheitlichkeit ausstrahlen. Sie muten aber, und das sei zur Ehrenrettung unseres Veteranen PC gesagt, nicht mit einer derart groen, (trgen) Masse installierter Einheiten leben und unter dem Zwang der Kompatibilitt alle dadurch hervorgerufenen Schwierigkeiten tragen. 
bersicht ber hardwarenahes Programmieren
 Ŀ    Ŀ   Ŀ                                             HARD-HARDWBIOS MSDOSC-       UNSER          WARE REGISINTERINTERBIBLI    PRO-                TER  RUPTSRUPTSOTHEK    GRAMM                                   <Ĵ                                   -----                                       DATEN<---                                                                          <Ĵ                              -----                                        DATEN<---------                                                                      <Ĵ                         -----                                         DATEN<---------------                                                                  <Ĵ                                                                   DATEN<---------------------                                                    SETUP                                   -----<---------------------------  BENUTZER       ͼ                            
Die obige geschichtete Struktur soll unsere 'Beziehung zum Rechner' noch einmal darstellen: 
Der Komputer prsentiert sich uns als geschichtetes Modell. Der Idealfall wre die Kommunikation mit der Bibliothek einer Hochsprache, die uns allen Hardwarerger vom Leibe hlt. Es gibt Beispiele, wo das ganz gut gelingt, z.B. Grafik, Textausgabe, Dateiverwaltung, Speicherverwaltung, usw. 
Wo die Funktionen der Hochsprache nicht ausreichen, etwa weil das Betriebssystem zu umfangreich ist, um jede noch so kleine Funktion in die Hochsprache zu integrieren, bietet uns jede Hochsprache Funktionen zu direkten Kommunikation mit dem Betriebssystem. An sich sollte hier die Hardwarenhe auch schon ein Ende haben, in der derzeitigen Form bietet aber das Betriebssystem nicht alle Funktionen (und die vorhandenen oft nicht in der erforderlichen Form oder Qualitt). 
Wir sind also gezwungen, eine Ebene tiefer, ins BIOS abzusteigen, wo sich das Spiel wiederholt und wir schlielich bei den Hardwareregistern selbst ankommen. 
Jeder kennt schlielich den Fall, da ein vorhandenes Programm trotz aller Bemhungen nicht laufen will, besonders hufig bei der Installation von Hardware zu beobachten, dann zieht der Benutzer das Stromkabel und manipuliert die Hardwarekonfiguration, die unterste Ebene, bis er die richtige findet: der Fachmann. 
In jeder Schicht, auer bei den Registern, gibt es Funktionen und Daten. Es wre wnschenswert, da die Programme nur mit den Funktionen kommunizieren. Damit wre sichergestellt, da nur diese, die betroffenen Daten (und richtig) modifizieren (Ansatz der objektorientierten Programmierung). Allerdings mten uns diese Schichten dann auch alle Funktionen zur Verfgung stellen, die wir zur Bedienung brauchen. (Sie knnen whrend unseres Rundganges durch den PC selbst feststellen, in welchem Ausma das derzeit der Fall ist.) Die durchgezogenen Linien (<) sind also die erste Wahl, die gestrichelten (<---) die Notlsungen. 
Die Hardwareschicht selbst und ihre Einstellung entzieht sich in vielen Fllen der Programmierung. Hier mu der Benutzer selbst die Einstellungen (Wahl der Adressen, Interrupts, Speicherbelegung usw.) vornehmen. 
Vorgangsweise
Wir wollen die Unterscheidung in Hardware-Programmierung - hardwarenahe Programmierung - systemnahe Programmierung dazu benutzen, unsere Reise durch das hardwarenahe Programmieren zu gliedern und bewegen uns, beginnend bei der zuunterst liegenden Hardware ber das hardwarenahe BIOS zum Betriebssystem. 
Wir bewegen uns bottom-up, d.h. beginnen bei einfachen Registeroperationen (ohne Benutzung vorhandener Software), werden die Interrupts kennenlernen, spter dann die BIOS- und MSDOS-Interrupts exemplarisch anwenden. Wir prfen die Hardwaregegebenheiten durch einfache Beispielprogramme. Immer wieder werden wir Gelegenheit haben, spezielle Probleme im Sinne einer Anwendung auszuprogrammieren. Der Zeitrahmen fr unsere Folge sind die etwa die nchsten zwei Jahre. 
Wenn wir ohne Einschrnkungen, mit Bedacht auf Vollstndigkeit, alles hardwarenah programmieren wollten, was mglich ist, entstnde ein dickes Buch, etwa wie Tischers PC-Intern. Daher ist Beschrnkung notwendig. Doch nach welchen Kriterien? 
Vorschlag: Wir programmieren nur das, was wir nicht in einer hheren Instanz 'gratis' bekommen knnen, also im Prinzip kein 'Tempobolzen' und 'Assembler um jeden Preis'. Darberhinaus versuchen wir uns nur an jenen Problemen, die der Demonstration von Hardwarezusammenhngen dienlich sind. Sie werden sehen, es bleibt genug zu tun! 
Wir werden mit einem grndlichen Schaltbild des PC beginnen, soweit dieses fr die Software wichtig ist und die IO-Registerbelegung und die Speicherbelegung besprechen. Die Qual der Wahl bei der Sprache sei hier vorweggenommen: Hochsprache, wo mglich, Assembler, wo ntig. Danach, etwa in folgender Reihenfolge: * IO-Register * Tastatur, Maus, Serielle Schnittstelle, Drucker, Game-Port * CMOS-RAM * Speicherbelegung des PC am BIOS-Niveau * Lautsprecher, Timer-Kanal 2 * Interrupts * Timer-Kanal-1 * Interruptvektoren umlenken * BIOS-Interrupts * MSDOS-Interrupts * Speicherverwaltung MSDOS * Expanded und Extended Memory * Speicherresidente Programme * Ausgewhlte Programme. 
Teil 2: Der PC
Franz Fiala, NT, TGM
Whrend wir in der ersten Folge unseres Vorhabens, des hardwarenahen Programmierens, eigentlich nur eine Standortbestimmung vorgenommen haben, mssen wir jetzt daran gehen, den PC-Aufbau zu skizzieren, damit die Wirkungsweise der Programme immer in diesem Hardwarezusammenhang betrachtet werden kann. 
Blockschaltbild PC
Wie so oft in sich entwickelnden Systemen liegt in der Vorgeschichte der Schlssel zum Verstndnis der gegenwrtigen Situation, lt aber auch Vermutungen ber zuknftige Entwicklungen zu. 
Daher ist es wichtig das Zusammenspiel der Peripheriebausteine und ihre Erreichbarkeit durch ein Programm zu kennen. Zunchst soll vorausgeschickt werden, da alle diese Bausteine am Datenbus liegen und ber eine oder mehrere Adreleitungen die Register innerhalb der ICs unterschieden werden. Die hherwertigen Adressen besorgen die Selektion des betreffenden Chips und die Unterscheidbarkeit von anderen. Wir knnen fr ein Softwaremodell auf das Zeichnen dieser Details verzichten. 
Sehen wir uns einmal die wichtigsten Bausteine an: Timer, Interrupt-Kontroller(PIC), DMA-Kontroller, Peripherie-Interface: alle diese Bausteine waren vor 10 Jahren schon entwickelt, damals teilweise fr eine 8-bit Prozessorfamilie, die 8080/85-CPU. Da die 8088 noch viele Merkmale einer 8-bit-CPU aufweist, fgten sich die Bausteine damals nicht einmal so schlecht in ein gemeinsames Gertekonzept. Das Einfrieren des 'Industriestandard', so vorteilhaft es einerseits auch war, lies die Entwicklung dieser Peripheriebausteine eher ins Stocken geraten, da wir uns noch immer mit dem Entwicklungsstand von vor 10 oder mehr Jahren beschftigen. 
Die hhere Integration in den CHIP-Sets brachte zwar eine Reduktion im Platzbedarf und der Leistungsaufnahme, nderte aber, wegen der erforderlichen Kompatibilitt, nichts an der Arbeitsweise der einzelnen Komponenten. Welche Einschrnkungen nehmen wir da in Kauf: 
Der Timer ist zwar ein 16-Bit-Timer, kann aber nur 8-bit-weise gesetzt und gelesen werden. Man knnte sich vorstellen, mehr Timer-Kanle zur Verfgung zu haben. Der Interrupt-Kontroller ist berhaupt ein zweigeteiltes Gebilde, sowohl fr 8080, als auch fr 8086-Betrieb ausgelegt, dafr aber zu wenig Kanle. Der DMA-Kontroller war nie fr 20- oder 24-bit-Adrebus gedacht, er kann nur eine 16-bit Start- und Stopadresse speichern. Die restlichen 4 Bit sitzen im IO-Speicher (siehe Belegungstabelle). Diese Aufzhlung liee sich weiter fortsetzen, man trifft beim Arbeiten 'an der Hardware' stndig auf solche Relikte. 

                           AT:PIC2/8259                            Ŀ                   Ŀ               Ŀ                          >ĴF                                                                                   >ĴE HD                                                                                >ĴD 80287                                                                             >ĴC       Ŀ                                                                         >ĴB                                                        Ŀ Ŀ       >ĴA                                                                              >Ĵ9 IRQ-2                                                  4,77  Tastatur        >Ĵ8 RTC                     Extended               Expanded  MHz                                     memory                 memory                      286:16MB               XT,AT,                           ĿPIC1/8259          386: 4GB               386:32MBĿ Ŀ      >Ĵ7 LPT        Ŀ DB                                       XT: 8255       >Ĵ6 FD         Parity ͵  XMS                  LIM/EMS   /4  AT: 8742       >Ĵ5 LPT         8+1 ͵                              Ŀ    >Ĵ4 COM1/3                  Haupt-                           >Ĵ3 COM2/4         NMI      speicher   4x16k              1,19 MHz         Ĵ2        INT Ŀ     1 MB      /Ŀ          Ŀ53msĴ1 KEYBD >Ĵ       Ŀ         / --------         ĴTIMER-0  Ĵ0 TIMER <Ĵ           Ĵ/  --------          Ĵ15us         INTA  CPU  x87 EMS-Page   --------͹   ĴTIMER-1  Ŀ  Ŀ                Ĵ---  Ŀ    Ĵ1ms Ŀ Ĵ DMA-0  >Ĵ           --------               Page-      ĴTIMER-2  ĴLS    <Ĵ       ٳBildsp. ->             register          ><Ĵ DMA-1            --------               A15..A24           8253             ͹                                                     FD  ><Ĵ DMA-2                                                 ͻ                             Ĵ                                                                                     ><Ĵ DMA-3                                                  Festplatte                                                                                                                        8237                                                  ͼ                             Ŀ                                              ͻ                          ><Ĵ DMA-4                               Ŀ                                           Ĵ                             64k             Floppy                               ><Ĵ DMA-5                               IO-Speicher                                             ͵        ͵              ͼ                           ><Ĵ DMA-6        Adrebus                -------------                                               Ĵ                              1k           ٳĿ128                          ><Ĵ DMA-7                                             ٳ CMOS-RAM                                                                                          AT:8237                      ^    ^       ^    ^                                                          MEMR MEMW    IOR  IOW

Beginnen wir bei der Besprechung des PC-Blockschaltbildes bei dem, was wir nicht sehen: Alle Peripheriebausteine werden ber den Adrebus dekodiert und ber den Datenbus programmiert. Bei den meisten besteht eine Verbindung zum Interrupt-Controller (Seriell, Parallel, Floppy, Festplatte), bei Floppy und Festplatte darberhinaus eine Verbindung zur DMA. 
Unterschiede der Rechnergenerationen
Einige Details des Blockschaltbildes sind nur am AT vorhanden oder einfach anders konstruiert. Um diese Besonderheiten besser zu verstehen, zeigt die folgende Tabelle die wesentlichen Unterschiede zwischen den Rechnergenerationen: 

	XT	AT
CPU	8088	80286
		erweiterter Befehlssatz
		neue Register,real/protected mode
Adrebus	20bit	24bit
Adrebereich	1MB	16MB
Speicherzugriff	8bit	16bit
Busstecker	2x31	2x31+2x18
Tastatur	Schieberegister	8742
		erweiterter Kursorblock mit neuen Tastenkodes
Uhr	-	Uhr und Alarm
Interrupts	8	15, zustzlicher Kontroller
DMA	4	8, zustzlicher Kontroller
Takt	4,77	6/10/16/20/25 MHz
Platine	2/3-size	full-size,2/3-size
Netzteil	-	Power-good-signal
BIOS	-	erweiterte Funktionen, 
Setup	DIP-Schalter	zuerst DISK, dann ROM
Koprozessor	8087	80287

	AT	386
CPU	80286	80386
		Befehlssatz, neue Register, neue Betriebsarten
Adrebus	24bit	32bit
Adrebereich	16MB	4GB
Speicherzugriff	16bit	32bit
Koprozessor	80287	80287 oder 80387

	386	386SX
CPU	80386	80SX386
Adrebus	32bit	24bit
Adrebereich	4GB	16MB
Speicherzugriff	32bit	16bit
Koprozessor	80387	80SX387

	386	486
CPU	80386	80486
Cache	-	8k eigebaut
Koprozessor	80387	eingebaut
Mit jeder neuen CPU ging auch eine Beschleunigung der Befehlsausfhrung einher, besonders bei der 486-CPU.
Bustypen
Bis zum Alleingang von IBM gab es nur den nunmehr als ISA-Bus benannten Bustyp (Industry Standard Architecture), der beim XT aus einem, spter, beim AT aus 2 Bussteckern bestand. 
Die ersten 386er hatten so ihre Not mit dem 32-bit-breiten Bus. Einerseits gab es damals noch nicht die RAM-ICs mit der heutigen Packungsdichte, soda es notwendig war, wollte man die Speicherzugriffsmglichkeiten nutzen, zumindest einen zustzlichen, ungenormten Stecker vorzusehen. Anderseits war IBM bereits aus der Vorreiterrolle ausgeschieden und ging mit dem Microchannel eigene Wege. Es gab keine einheitliche (Bus)Linie bei den 386ern. Jeder Erzeuger hatte so seinen Erweiterungsstecker. Bis die hheren Packungsdichten bei den RAMs auf den Markt kamen, dann konnte man den 32-bit-Bus voll auf der Hauptplatine halten. Das ist auch der heutige, ziemlich unbefriedigende Zustand. 
Die IBM-Neuentwicklung Micro-Channel ist ein 32-bit-Bus, unsere (ISA-Bus-)Erweiterungskarten passen dort nicht hinein und umgekehrt. Wie die Marktverbreitung auerhalb der IBM-Welt zeigt, kein guter Weg, zumindest nicht fr uns Anwender; man will nicht von einem Konzern abhngig sein. 
Die Gemeinschaftsentwicklung EISA-Bus, ebenfalls ein 32-bit-Bus, versucht die Zugkraft des IBM-Riesen, wie auch in anderen Normungsfragen (siehe LIM-Standard), durch eine Firmengruppe auszugleichen. Dabei wird der aufwendige Versuch unternommen, einen 32-bit-Bus zu schaffen und gleichzeitig die mechanisch-elektrische Kompatibilitt zum ISA-16-bit-Bus aufrechtzuerhalten. Es gelingt mit verlngerten Kartensteckern und bereinander liegenden Kontakten: ein elektronisch-mechanischer Klimmzug und alles nur um die Kompatibilitt zu erhalten. Die Folge: sehr teure Grundgerte, die man zunchst ebenso selten zu Gesicht bekommt, wie passende Erweiterungskarten dazu, wir mssen abwarten.
Speicher im PC
Aus der Sicht der 80x86-CPU gibt es zwei Speichertypen: Hauptspeicher (1MB) und IO-Speicher (64k). Im PC sind von den mglichen 64k nur 1k dekodiert. In diesen 1024 Adressen sind alle IO-Register enthalten. Eine Ausnahme ist der Bildspeicher, er ist im Hauptspeicher zu finden. Genaugenommen sollten wir die Register der CPU ebenfalls zum Speicher zhlen. Die Nachfolger der 8086-CPU (80286,80386,80486) kennen darberhinaus noch den Speicherbereich ber 1 MB, das extended memory. 
Aus der Sicht des PC ist der Hauptspeicher bis 640k kontinuierlich als RAM ansprechbar, darberhinaus ndert sich die Belegung, je nach Rechnertype. Es werden auer RAM-Speicher auch noch Video-Speicher, ROM-Speicher oder Nichts gemappt. Der IO-Speicher dagegen ist  von vornherein 'lchrig'. Auf festgelegten Adressen findet man mehr oder weniger punktuell Register der verschiedensten IO-Bausteine. 
Aus der Sicht des Systems PC gibt es aber noch weitere Speicher, z.B. das CMOS-RAM, die Massenspeicher Floppy und Festplatte sowie das bisher nicht erwhnte expanded memory. Diese sind alle ber IO-Adressen erreichbar. 
Es ist zwar komisch CMOS-RAM und Floppy in einem Satz zu nennen, erscheinen sie uns doch sehr verschieden, erreichbar sind ihre Inhalte aber immer ber IO-Adressen. Einen wichtigen Unterschied gibt es aber doch: Den eigentlichen Datentransport bernimmt bei den Massenspeichern ein eigener Transportmechanismus, der DMA (Direct Memory Access = Direkter Speicherzugriff ohne CPU-Mitwirkung), whrend es beim CMOS-RAM nicht so eilig zugeht. 
Der IO-Speicher unterscheidet sich von dem bekannten einen Megabyte Hauptspeicher, aber wie? Der Befehlssatz unseres Prozessors kennt Befehlsgruppen mit verschiedener Reaktion nach auen. 
Es gibt Befehle, die keine Reaktion nach auen zeigen. Alle Vernderungen und Datentransporte betreffen Datenpfade innerhalb der CPU (NOP; MOV AX,BX; ADD AX,DX; INC DI; ...). 
Weiters gibt es eine groe Gruppe von Befehlen, die einen Zugriff zum Hauptspeicher bewirken, wie MOV AX,CS:[200], ADD BX,DS:[DI], PUSH BP ... Entweder sind sie am Bezug zu einer Adresse ([..]) zu erkennen oder der Zugriff ist, wie bei PUSH-Befehlen, implizit im Befehlskode enthalten. Allen diesen Befehlen gemeinsam ist die Aktivierung der Leitungen /MEMW und /MEMR, je nachdem, ob der Datentransport von oder zur CPU gerichtet ist. 
Schlielich gibt es noch Befehle, die statt /MEMW und /MEMR die Leitungen /IOW und /IOR aktivieren und so einen Hauptspeicherzugriff von einem IO-Zugriff unterscheidbar machen. Sinnvollerweise heien diese Befehle auch IN und OUT und knnen als Operanden das AX- oder AL-Register haben. IO-Adresse kann im Adrebereich 0..255 eine direkte Adresse sein (INAL,23H) oder im Adrebereich 0..1023 (INAL,DX) eine Adresse im DX-Register. 
Bei unserer CPU-Type wird also zwischen Speicher, in dem Programme und Daten abgelegt sind und Speicher, der Ein- und Ausgnge nach auen hat, unterschieden. Man spricht vom 'IO-mapped' IO. Auch die Vorgngertypen unserer CPU (8080/85, 8008) hatten dieses Konzept, es hat also Tradition. Der Vorteil: Es ist im Programmlisting eindeutig erkennbar, was eine IO-Operation ist und was nicht. Man kann gezielt nach solchen Kodes suchen, etwa zur Programmanalyse! 
Es sei aber nicht unerwhnt, da es auch anders geht: Die IO-Adressen knnen auch 'memory-mapped' irgendwo im Hauptspeicher enthalten sein, wie bei den MOTOROLA-CPUs oder bei den Mikro-Kontrollern. Der Vorteil: Smtliche Adressierungsarten, die fr den Hauptspeicher gelten sind auch fr die IO-Bausteine anwendbar. 
Wenn wir das PC-Konzept in dieser Richtung betrachten, sehen wir, da auch hier eine Art von 'memory-mapped'-IO in der Form des Bildschirmspeichers existiert (Speicheradressen 0xA0000-0xBFFFF). Daher benutzen alle Programme, bei denen Geschwindigkeit im Vordergund steht (z.B. Spiele) die Mglichkeit, direkt in den Bildschirmspeicher zu schreiben. 
Unterschied Register-Speicherplatz
Was ist eigentlich der Unterschied zwischen einem Speicherplatz und einem Register? 
Register sind i.a. Speicherpltze, die ber das reine Speichern von Daten hinaus, auch Funktionen mit diesen Daten ausfhren knnen. Sei es nach auen, wie bei Ein- und Ausgabeleitungen oder mit den Daten selbst, wie bei der CPU oder Steuerungsfunktionen, wie bei Kommando- oder Statusregistern. 
Die Registerein- und -ausgnge sind nicht immer unmittelbar an Pins herausgefhrt, sondern enden als Steuerleitungen irgendwo in den komplexen hochintegrierten IO-Bausteinen. Sind sie herausgefhrt, knnen wir sie als Spannung an den Pins etwa der parallelen oder seriellen Schnittstelle oder der Tastatur messen. 
Register knnen entweder nur gelesen oder nur beschrieben werden oder sie sind schreib- und lesbar. Es gibt einige wichtige Register im PC, die nur beschreibbar sind. Der Nachteil ist, da sich kein Programm ber den aktuellen Zustand Gewiheit verschaffen kann, es ist also darauf angewiesen entweder, zu wissen, wo der aktuelle Zustand des Registers im Hauptspeicher notiert ist, oder, sie mssen alles neu programmieren, dann aber knnen sie den ursprnglichen Zustand nicht wiederherstellen. 
Der Timer ist ein Beispiel dafr: man kann zwar den Timerwert zurcklesen aber nicht die Einstellparameter, man wei also nicht, wie ein Timer eingestellt ist, um ihn wieder auf denselben Wert zu bringen, d.h. man mu ihn wieder auf den Wert zu Betriebsbeginn einstellen (dieser Wert ist im BIOS dokumentiert). Was aber, wenn ein geladenes residentes Programm die Timerzeitkonstante verndert hat und jetzt mit einem falschen Wert arbeitet? Leider wird diese Einstellung des Timers nicht durch eine BIOS-Variable, die den aktuellen Zustand kennt, ausgeglichen. Ein Programm, beispielsweise ein ein Multitaskingprogramm, das auf einer bestimmten Timereinstellung aufbaut, ist mit allen Programmen automatisch inkompatibel, die den Timer ihrerseits verndern. 
Achten wir also bei Konstruktionen darauf, da wichtige Steuerinfomationen in Registern rcklesbar sind, da es zu keinem unzweckmigen 'WOR'(write-only-Register) kommt.
IO-Bereich im PC
Die Ein- und Ausgabeleitungen am PC sind ber die Assembler-Befehle IN und OUT erreichbar. In der Sprache C werden diese Befehle durch die Funktionen inportb() und outportb() reprsentiert. Der Prozessor erlaubt 64k Ein- und Ausgabekanle, von denen im PC 1024 dekodiert sind. Die ersten 256 Adressen liegen auf der Hauptplatine. 
Aus der Sicht der CPU ist der IO-Bereich gleichrangig mit dem Hauptspeicher. Die Unterschiede sind die Gre und die auf IN und OUT beschrnkte Zugriffsmglichkeit. Die Hardwaresteuerung der IO-Bausteine entspricht aber im Prinzip denen des Hauptspeichers. 
Aus der Sicht des Benutzers befindet sich aber im IO-Bereich kein kontinuierlich ansprechbarer Speicher, vielmehr teilen sich diesen Speicher viele Peripheriebausteine mit den verschiedenartigsten Ansteuermechanismen. 
Fr die Ansteuerung der IO-Adressen bentigt man im allgemeinen Spezialkenntnisse ber die konkrete Verdrahtung am PC. Die wichtigste Quelle fr dieses Wissen sind das Technische Refenz-Handbuch fr den PC und fr den AT, die smtliche Schaltplne enthalten. Einige dieser Details werden wir hier besprechen. Die zweite Quelle sind die Datenbltter der betroffenen IO-Bausteine, diese finden Sie in TGM-LIT-032. 
Die Register lassen sich grob in folgende Gruppen unterteilen:
1. Datenregister (Ein- und Ausgabe)2. Steuerregister (Command, Control)3. Statusregister
Die Datenein- und -ausgaberegister haben unmittelbare Verbindung mit der Auenwelt. Ein Beispiel dafr ist die Druckerausgabe. Ein auf dem Druckerport ausgegebenes Byte kann in Form von Spannungspegeln unmittelbar an den Stiften des Centronics-Steckers gemessen werden. Es ist ein rcklesbarer Datenport, d.h. man kann auf derselben Adresse auch durch Lesen erfahren, welches Datenbyte zuletzt ausgegeben wurde. Es geht auch anders: Beim seriellen Port kann man das letzte gesendete Byte nicht zurcklesen. 
Die Steuerregister haben ihre Wirkung im allgemeinen als Steuersignal irgendwo im inneren Aufbau der ICs. Hier ist es auch vorteilhaft, den aktuellen Zustand erfragen zu knnen. Kann man es nicht, mu man sich selbst (oder mu sich das BIOS) den aktuellen Wert merken, andernfalls ist man nicht mehr in der Lage, den ursprnglichen Zustand wiederherzustellen. Der Timer ist ein solcher Fall: Wten wir nicht auf Grund des BIOS-Listings, wie er eingestellt ist, knnten wir ihn nur mit Mhe fr andere Zwecke einsetzen. 
Die Statusregister geben uns Aufschlu ber den aktuellen Zustand der Peripherie. 
Im allgemeinen ist in Programmen jeder Zugriff auf dieser untersten Ebene untersagt, da darberliegende Instanzen (BIOS, DOS usw.) die Ein- und Ausgabeleitungen kontrollieren. Man sollte stets die Regel beachten, die hchst mgliche Ebene fr ein bestimmtes Problem anzuwenden. Wir werden nach und nach diese Ebenen besprechen. 
Da wir uns aber zur Aufgabe gemacht haben, harwarenahes Programmieren zu praktizieren, wollen wir diese Regel fallweise auer Acht lassen. 
Belegung der IO-Adressen im PC

IC-Bez	Adresse	XT/AT	Name

	Hauptplatine

8237	000-00f		DMA #1
	010-01f		reserviert
8259	020-02f		Interrupt-Controller #1
	030-03f		reserviert
8253	040-04f		Timer
	050-05f		reserviert
8255	060-06f	X	Peripherie (Tastatur, DIP-Schalter, Parity-Check...)
8742	060-06f	A	Tastatur-Controller
	070-07f	A	CMOS-RAM & NMI Mask-Register
LS612	080-08f		DMA-Page-Register
8259	0a0-0af	A	Interrupt-Controller #2
	0a0-0af	X	NMI-Mask-Register
	0b0-0bf		reserviert
8237	0c0-0cf	A	DMA #2
	0d0-0df		reserviert
	0e0-0ef		reserviert
80287	0f0-0ff	A	Coprozessor

	Erweiterungsslots

	100-16f		verfgbar
1003	170-177		Hard disk (secondary)
	178-1ef		verfgbar
1003	1f0-1ff	A	Hard Disk
	200-20f		Game-Port
	208-209		EMS-Base Address Register
	210-21f	X	Expansion-Unit
	218-219		EMS-Base Address Register
	220-237		verfgbar
	238-23b		Bus Mouse
	23C-23f		Alternate Bus Mouse
	240-277		verfgbar
	258-259		EMS-Base Address Register
	268-269		EMS-Base Address Register
	278-27f		Parallel Printer #2 (#3 with MDA)
	280-2af		verfgbar
	2a8-2a9		EMS-Base Address Register
	2b0-2bf		EGA
	2b8-2b9		EMS-Base Address Register
	2c0-2cf		EGA #2
	2d0-2df		EGA
	2e0-2e7		GPIB
8250	2e8-2ef		Serial Port
	2e8-2e9		EMS-Base Address Register
	2f0-2f7		reserviert ?
8250	2f8-2ff		Serial Port #2
	300-30f		Prototype Card
	310-31f		Prototype Card
1003	320-32f	X	Hard Disk
	330-36f		verfgbar
765	370-377		Diskette Controller (secondary)
	378-37f		Parallel Printer #1 (#2 with MDA)
	380-38f		SDLC
	3a0-3af		bisynchronous port #1
6845	3b0-3bb		MDA, HGC
	3bc-3bd		Parallel Printer on MDA
	3c0-3cf		EGA
6845	3d0-3df		CGA
	3e0-3e7		verfgbar ?
8250	3e8-3ef		Serial Port
765	3f0-3f7		Floppy Disk
8250	3f8-3ff		Serial Port
'verfgbar' in dieser Tabelle mu nicht heien, da es keine Erweiterungskarte gibt, die diesen Adrebereich belegt, sondern nur, da in den zur Verfgung stehenden Unterlagen keine Anwendung angegeben wurde. 'reserviert' bedeutet i.a., da keine vollstndige Dekodierung vorliegen drfte. 
Die in dieser Tabelle enthaltenen Adressen sind IO-Adressen, von denen der 80x86 65536 kennt, der XT,AT,386,486 aber nur 1024 verwendet [Beachten Sie den Unterschied zwischen den 64k, die die CPU ansprechen knnte und dem einen Kilobyte, das der Rechner ansprechen kann]. Der PC verwendet nur 10 der 16 Adreleitungen zur Dekodierung. Dieser Adrebereich ist bis zur Adresse 255 auf der Hauptplatine dekodiert und darber hinaus teilen sich die verschiedenen Interfacekarten (Video-, Serielle-, Parallele-, Floppy und Festplattenkarten uvam.) den Adrebereich von 256 bis 1023. 
Hauptspeicher im PC
Speicherbereiche im PC
Die Speicheraufteilung erfolgt auf verschiedenen Ebenen. Zuerst durch die Erfordernisse der CPU, dann durch die des BIOS und schlielich durch die des verwendeten Betriebssystems MSDOS oder OS/2. 
Der Speicherraum wird in 64k-Segmente unterteilt, die wieder in 16k-Abschitte zerlegt werden und auch feiner, je nach Bedarf. Wichtiges beginnt immer mit einem neuen 64k-Segment. 
Das BIOS und die Hardware sind untrennbar verbunden, werden also gemeinsam dargestellt. Das nachfolgend geladene MSDOS kann die ersten Vernderungen an den Voreinstellungen des BIOS vornehmen und tut dies durch den Programmteil IBMBIO.COM. 
Hauptspeicher aus Sicht der CPU
Der Hauptspeicher der 8088/86-CPU ist 1MB gro. Unterteilt man ihn in Segmente, wobei jedes Segment 64k umfat, zhlen wir 16 solche Segmente. Um zu sehen, welche Speicherkonfiguration die CPU fordert, studiert man am besten das Datenblatt: Da ist zunchst der sogenannte Reset-Vektor. Das ist jene Adresse, bei der der Ablauf jeder Komputerexistenz beim Einschalten seinen Anfang nimmt: f000:fff0h=ffff0h. Zum Einschaltzeitpunkt mu sich an dieser Stelle ffff0h ein Festwertspeicher (ROM) befinden, das BIOS. 
Weiters kennt die CPU eine Gruppe von Befehlen, die sogenanten Software-Interrupts, INT 0..INT FF, deren Aufruf bewirkt, da die CPU ihre Ttigkeit an einer Adresse fortsetzt, die sich aus der mit 4 multiplizierten Interruptnummer ergibt. Da es 256 verschiedene Interrupts gibt, sind also die ersten 1024 Bytes im Hauptspeicher durch diese Interruptvektoren belegt. Von diesen 256 Interruptvektoren beansprucht die CPU die ersten, die anderen Vektoren kann der Anwender benutzen. Das wars, mehr Ansprche hat die CPU nicht, was den Speicher anlangt. Das Weitere ist schaltungs- und softwarebedingt. 
Hauptspeicher aus Sicht des PC,BIOS
Der PC beschrnkt die Verwendung des Hauptspeichers auf die Adressen von 00500 bis 9ffff. Im Segment 40 (ab 00400 bis 004ff) sind 256 Bytes mit BIOS-Daten gefllt. Ab der magischen Grenze von 640k (=a0000) sind, je nach Bestckung mehr oder weniger viele Bereiche durch Bildspeicher (RAM) oder BIOS und BIOS-Erweiterungen bestckt. 
Hauptspeicher aus Sicht des Betriebssystems
Beim Einschalten des PC durchluft das BIOS eine Vielzahl von TEST-programmen, die unter dem Sammelnamen POST zusammengefat sind. Die letzte Handlung dieses Programmteils ist der Versuch, entweder vom Laufwerk A: oder, wenn das nicht gelingt, weil keine Diskette eingelegt ist, vom Festplattenlaufwerk zu booten. In den meisten Fllen ist jetzt MSDOS an der Reihe, es knnte aber auch eine Version von UNIX, OS/2 oder DRDOS geladen werden. Jedenfalls wird jetzt der Hauptspeicher weiter strukturiert. 
Whrend in frhen PC/AT-Konstruktionen der Hauptspeicher tatschlich bei 640k aufhrte, haben heutige Rechner einen durchgehenden RAM-Speicher, daher kommt es oberhalb von 640k zu Doppelbelegungen, die auf mehrfache Weise nutzbringend verwendet werden knnen. Durch das sogenannte shadowing knnen langsame Video-RAM-Bereiche oder langsame BIOS-ROM-Bereiche in das schnelle RAM des Hauptspeichers kopiert werden. Die nach wie vor freien Hauptspeicher-RAM-Bereiche, die keinen Zusammenhang mit den ersten 640k haben, werden in der DOS-Diktion als UMB (Upper-Memory-Blocks) bezeichnet, sie werden bei der neuen DOS-Version 5.0 (oder von sogenannten Memory-Managern) durch Systemprogramme oder residente Programme des Benutzers gefllt. Man kann aber auch den gesamten RAM-Speicher oberhalb von 640k als extended memory schalten, soda die Mglicheiten des shadowing oder der Ausnutzung der UMBs zwar entfallen, dafr aber auch die gesamten 384k als geschlossenes extended memory zur Verfgung stehen (diese Mglichkeit wird derzeit eher nicht benutzt, da es zunehmend auf einige k nicht mehr ankommt). 
Der Restspeicher TPA (Transient-Program-Area) steht dem Benutzerprogramm zur Verfgung. Wenn das zu wenig ist, kann man durch einen kleinen Kunstgriff noch etwa 64k des extended memory dem Betriebssystem zur Verfgung stellen: die HMA (High Memory Area), das sind die ersten 64k-16 Bytes ber der 1MB-Grenze. Das bedeutet nicht, da das DOS pltzlich in der Lage wre extended memory anzusprechen. Es geht wirklich nur in diesem kleinen Abschnitt. Im allgemeinen wird ein Groteil des Betriebssystem dorhin verschoben, was dem Benutzer zustzlichen Hauptspeicher freimacht. 
Jede Adresse des Hauptspeichers wird durch die CPU im sogenannten real mode durch die Addition einer Segmentadresse und einer Offsetadresse angesprochen: 
adr = seg:off = seg*16 + off
Die hchste Adresse bei einem XT war somit fffff = f000:ffff = ffff:000f ==... Man sieht, da man dieselbe 20-Bit-Adresse auf Grund verschiedenartiger Einstellung von Segment und Offset erzielen kann. Auerdem kann bei einer solchen Addition auch etwas grere Summen als fffff gebildet werden, die hchste Adresse ist ffff:ffff = 10ffef. Beim XT ist die fhrende 1 als Adreleitung nicht verfgbar, daher erhlt man eine Adresse ganz unten im Speicher (0ffef), sie wird 'gefaltet'. Im AT hngt das Verhalten davon ab, ob die Adreleitung A20 ein- oder ausgeschaltet ist. Ist sie ausgeschaltet, verhlt sich der AT wie ein XT, sonst eben ein bichen anders, und DOS kann diesen zustzlichen Speicher adressieren, indem die Summe aus Segment und Offset zum Maximum hin ausgenutzt werden. Das Ausschalten der A20-Leitung dient nur zur Aufrecherhaltung der Kompatibilitt zu lteren XT-Programmen, die den Falteffekt bentigen. 

CPU                  BIOS                 MSDOS                                                              extended           extended           extended          memory             memory             memory            (ab 286)           (ab 286)           Ŀ                                             HMA            Ĵ       Ĵ             ------------ffff0   BIOS               BIOS             Reset-Vektor       Ĵf0000  Ĵf0000                                                                             wechselnde         UMB                                  bestckgs-         punktuell                            bedingte           fr DOS                              Inhalte            nutzbar                                                                                     Ĵa0000  Ĵa0000                    RAM         9ffff              9ffff                                                                                                                                                           TPA                                                                                                                                                                                                                         Ĵ                                             IBMDOS.COM                                              IBMBIO.COM                                             Ĵ                                             MSDOS-                                                  Daten                               Ĵ       Ĵ                          BIOS-      004ff   BIOS-      004ff                     Daten              Daten            Ĵ       Ĵ00400  Ĵ00400                                003ff   Interrupts 003ff                                        30-ff                                                  Ĵ                                             MSDOS-                                                  Interrupts                                              20-2f                               Ĵ       Ĵ                          BIOS-              BIOS-                                Interrupts         Interrupts                           10-1f              10-1f                               Ĵ       Ĵ                          Hardware-          Hardware-                            Interrupts         Interrupts                           08-0f              08-0f            Ĵ0001f  Ĵ00020  Ĵ00020   CPU-       0001f   CPU-       0001f   CPU-       0001f  Interrupts         Interrupts         Interrupts        00-07              00-07              00-07            00000  00000  00000  

Wie man aus der vergleichenden Darstellung sieht, sind gewisse Speicherbereiche systembedingt definiert. Wir werden die Speicherbereiche der Reihe nach analysieren, jeweils mit selbstgeschriebenen Utilities. 
Speicheraufteilung
start   start  end (dec)      (hex) 00000 **** 640k *************** system data, drivers....                         0000:0000  hardware interrupt vectors                         0000:0040  BIOS interrupt vectors 0k      start of RAM |  0000:0080  DOS interrupt vector table 16k     00000-03FFF  |  0000:0300  Stack area during POST and bootstrap routine 32k     04000-07FFF  |  0000:0400  BIOS Data Area 48k     08000-0BFFF  |  0000:04F0  Intra-Application Communications Area                      | 64k     10000-13FFF  |  0000:0500  DOS reserved communication area 80k     14000-17FFF  |  xxxx:0000  IO.SYS - DOS interface to ROM I/O routines 96k     18000-1BFFF  |  xxxx:0000  MSDOS.SYS - DOS interrupt handlers, service 112k    1C000-1FFFF  |             routines (int 21 functions)                      | 128k    20000-23FFF  |  xxxx:xxxx  DOS buffers, control areas, and installed 144k    24000-27FFF  |             device drivers. 160k    28000-2BFFF  |  xxxx:0000  resident portion of COMMAND.COM, interrupt 176k    2C000-2FFFF  |             handlers for int 22h, 23h,24h, and code to                      |             reload the transient portion 192k    30000-33FFF  |  xxxx:0000  master environment block, default 64 bytes 208k    34000-37FFF  |  xxxx:0000  environment for next program 224k    38000-3BFFF  |  xxxx:0000  external commands or utilities (COM or EXE 240k    3C000-3FFFF  |             files)                      | 256k    40000-43FFF  |  ----:----  application programs 272k    44000-47FFF  |  xxxx:0000  user stack for COM files (256 bytes) 288k    48000-4BFFF  |  xxxx:0000  transient portion of COMMAND.COM 304k    4C000-4FFFF  |                      | 320k    50000-53FFF  | 336k    54000-57FFF  | 352k    58000-5BFFF  | 368k    5C000-5FFFF  |                      | 384k    60000-63FFF  | 400k    64000-67FFF  | 416k    68000-6BFFF  | 432k    6C000-6FFFF  |                      | 448k    70000-73FFF  | 464k    74000-77FFF  | 480k    78000-7BFFF  | 496k    7C000-7FFFF  |                      | 512k    80000-83FFF  | 528k    84000-87FFF  | 544k    88000-8BFFF  | original IBM PC-1 BIOS limited memory to 544k 560k    8C000-8FFFF  |                      | 576k    90000-93FFF  | 592k    94000-97FFF  | 609k    98000-9BFFF  | 624k    9C000-9FFFF  | to 640k (top of RAM address space) A0000 ***** 64k *************** EGA address 640k    A0000-A95B0  MCGA 320x200 256 color video buffer               AF8C0  MCGA 640x480 2 color video buffer              -A3FFF 656k    A4000-A7FFF 672k    A8000-ABFFF 688k    AC000-AFFFF B0000 ***** 64k *************** mono and CGA address 704k    B0000-B3FFF  mono uses only 4k        | The PCjr and early Tandy 1000 720k    B4000-B7FFF                           | BIOSs revector direct writes to 736k    B8000-BBFFF  CGA uses entire 16k      | the B8 area to the Video Gate 756k    BC000-BFFFF                           | Array and reserved system RAM 
                     B000:0000-B000:7FFF Hercules, P.1                     B000:8000-B000:FFFF Hercules, P.2                     B800:0000-B800:3FFF CGA-Graphic                     B800:0000-B800:0FFF CGA-P.1(Text)                     B800:1000-B800:1FFF CGA-P.2(Text)                     B800:2000-B800:2FFF CGA-P.3(Text)                     B800:3000-B800:3FFF CGA-P.4(Text)
C0000 ***** 64k *************** expansion ROM 768k    C0000-C3FFF  16k EGA BIOS C000:001E EGA BIOS signature (the letters IBM) 784k    C4000-C5FFF         C6000-C63FF  256 bytes Professional Graphics Display communication area         C6400-C7FFF 800k    C8000-CBFFF  16k hard disk controller BIOS, drive 0 default 816k    CC000-CDFFF  8k  IBM PC Network NETBIOS         CE000-CFFFF 
D0000 ***** 64k *************** expansion ROM | PCjr first ROM cartridge 832k    D0000-D7FFF  32k IBM Cluster Adapter  | address area.         DA000        voice communications     | 848k    D4000-D7FFF                           | Common expanded memory board 864k    D8000-DBFFF                           | paging area. 880k    DC000-DFFFF                           | 
E0000 ***** 64k *************** expansion ROM |    PCjr second ROM 896k    E0000-E3FFF                           |    cartridge address 912k    E4000-E7FFF                           |    area 928k    E8000-EBFFF                           | 944k    EC000-EFFFF                           | 
F0000 ***** 64k *************** system        |    PCjr optional ROM 960k    F0000-F3FFF  reserved by IBM          |    cartridge address 976k    F4000-                                |    area (cartridge         F6000        ROM BASIC Begins         |    BASIC) 992k    F8000-FB000                           | 1008k   FC000-FFFFF  ROM BASIC and original   |                      BIOS (Compatibility BIOS |                      in PS/2)                 | 1024k   FFFFF   end of memory (1024k) for 8088 machines                      F000:FFF5 BIOS release date                      F000:FFFE PC model identification 
384k    100000-15FFFF  80286/AT extended memory area, 1Mb motherboard 15Mb    100000-FFFFFF  80286/AT extended memory address space 
15Mb    160000-FDFFFF  Micro Channel RAM expansion (15Mb extended memory) 128k    FE0000-FFFFFF  system board ROM            (PS/2 Advanced BIOS) 
Erweiterungsspeicher und Zusatzspeicher
Beim AT und allen folgenden PC-Generationen erfolgte eine Erweiterung des Hauptspeichers durch Vergrerung der Adrebusbreite. Dieser Speicher ber 1MB wird als extended memory bezeichnet. Extended memory ist eine Eigenschaft der CPU 80286 und aller Nachfolger.
Interessanterweise wurde in den Anfngen des AT das extended memory weniger verwendet, als das sogenannte expanded memory, ein Zusatzspeicher, der seitenweise (1 Seite=16k) in den Hauptspeicher unterhalb 1MB eingeblendet wurde. Das Paging (Umschalten zwischen den Speicherseiten des expanded memory) besorgen eigene Hardwareregister auf den Speichererweiterungskarten. Expanded memory war anfangs eine Eigenschaft von Erweiterungskarten, die nach dem sogenannten LIM-Standard gefertigt wurden. (LIM=Lotus-Intel-Microsoft). 
Der LIM-Standard beschreibt die Hardwarespezifikation der Erweiterungskarten, d.h. die Adressen der Page-Register und die Bedeutung der einzelnen Bits der Page-Register. Die gleichzeitig erforderliche Softwareschnittstelle heit EMS-Spezifikation. 
Spter wurde im AT der auf der Hauptplatine verfgbare Speicher ber kleine Brcken (Jumper) wahlweise als extended oder expanded memory schaltbar gemacht, die Erweiterungsplatinen wurden praktisch entbehrlich. Diese Umschaltung war mehr oder weniger flexibel. 
Die nchste Entwicklungsstufe waren die Chip-Stze, die dieselbe Umschaltarbeit per Software im sogenannten SETUP-Programm wesentlich flexibler ermglichen. 
Mittlerweile ist expanded memory nur mehr in 'Fossil'-oder 'Saurier'-Anwendungen anzutreffen, d.h. entweder in sehr alten aber immer noch in Betrieb befindlichen Systemen (Beispiel LOTUS-123, Vers.3) oder in sehr groen und schwerfllig anpabaren Anwendungen (Beispiel PCAD). 
Der Grund fr die anfngliche Bevorzugung des expanded memory war, da man expanded memory auch im XT einsetzen konnte und die Programme demnach nur diese Form der Speichererweiterung ansprechen knnen muten. Erst nach Aufkommen der Speichermanager, wie QEMM-386, und den neuen Betriebssystemversionen MSDOS-5.0 und DRDOS-6.0 wurde es einfach mglich, extended memory bei Bedarf als expanded memory einzusetzen. Vor dieser Zeit war es erforderlich, durch Umkonfigurieren der Hardware eine andere Speicheraufteilung zwischen extended und expanded memory einzustellen. 
Zeitbasis
Wichtig ist zunchst die Taktaufbereitung fr den TIMER, die eine zum XT abwrtskompatible Zeitbasis fr Programme bereitstellt. Wenn auch viele Teile im AT umkonstruiert wurden, die Zeitbasis, die dem Timer angeboten wird, ist der durch 4 geteilte 4,77 MHz-Takt. 
TIMER-Kanal-0 erzeugt aus diesem Basistakt durch Teilung durch 65536 Impulse an der Interruptleitung IRQ0, die den Hardwareinterrupt INT-8 im Abstand von 55ms (18,2s1) auslsen. Wenn sonst nichts anderes zu tun ist (d.h. keine anderen Aktionen von residenten Programmen durch Einschaltung in die Interruptkette gefordert werden), wird jedenfalls eine Zhlvariable mit 4Bytes und einem berlauf fr die Zeit seit Mitternacht weitergezhlt; zustzlich initiiert der INT-8 den Interrupt 1ch fr Anwendungen. 
TIMER-Kanal-1 sorgt fr die Ansteuerung von DMA-Kanal0 im Abstand von etwa 15us, worauf dieser eine weitere Adresse am Adrebus anlegt und diese Adresse um 1 erhht, soda fr einen kontinuierlichen Speicherrefresh gesorgt ist. [Die Information ist beim dynamischen RAM in kleinen Kapazitten gespeichert. Die Information ginge verloren, da sich die Kapazitten entladen. Der regelmige Refresh 'pumpt' zustzliche Ladung in die Kapazitt.]
TIMER-Kanal-2 ist ber eine kleine Logik mit dem Lautsprecher verbunden, und normalerweise ber ein Gatter von diesem getrennt. Die Teilungsziffer ist so gewhlt, da beim Einschalten des Gatters und des Timers genau 1 kHz ausgegeben wird. 
Direkter Speicherzugriff (DMA)
Bei gewhnlichen Programmieraufgaben geht man davon aus, da alle Datentransporte den Umweg ber die CPU nehmen. Die 80x86-CPU ist eine sogenannte Einadremaschine, d.h. in einer Operation knnen die Daten zwischen Speicher und der CPU ausgetauscht werden. Sollen zwei unabhngige Daten beispielsweise addiert werden und das Ergebnis an einem dritten Speicherplatz abgelegt werden, bedarf es dazu vier Befehle.
(Z3) <- (Z1) + (Z2)
in Assembler:
     MOV  AX,Z1     MOV  BX,Z2     ADD  AX,BX     MOV  Z3,AX
Diese Konzeption ist in der Struktur des Befehlssatzes begrndet. 
Sollen Daten vom IO-Speicher zum Hauptspeicher gebracht werden (oder umgekehrt) ist immer etwa folgende Befehlsfolge ntig:
inp: IN   AL,DX      ; in DX ist die IO-Adresse     MOV  [BX],AL    ; in BX ist die Hauptspeicheradresse     INC  BX     LOOP inp        ; in CX ist ein Zhler
Fr groe Datenmengen ist die CPU mit diesem Programm ein Flaschenhals. Das ist speziell bei den Massenspeichern aber auch bei abtastenedem Messen mit AD-Wandlern der Fall. Fr diese Aufgaben sind die beiden DMA-Kontroller gedacht. Der DMA-Controller ist, genauso wie die CPU, berechtigt Adressen auszusenden und wird per Software dazu beauftragt. Das Programm legt Anfangs- und Endadresse der bertragung fest und startet die bertragung. 
DMA-Kanle
Kanal	XT/AT	Zugriff	Nutzung0	X+A	8bit	Memory Refresh1	X+A	8bit	SDLC2	X+A	8bit	Floppy-Disk3	X+A	8bit	verfgbar4	A	16bit	verfgbar5	A	16bit	verfgbar6	A	16bit	verfgbar7	A	16bit	verfgbar
Unterbrechungen
Hardwareunterbrechungen (HWI)
Die Ein- und Ausgabeeinheiten unseres PC sind unterbrechungsgesteuert (interruptgesteuert); es besteht eine logische Verbindung zwischen einem Ausgang der Peripherie und dem INT-Eingang der CPU. Die Reihung dieser Unterbrechungsanforderungen nimmt ein kaskadierter Interrupt-Kontroller vor. Wir mssen zwischen diesen Hardwareinterrupts und den spter zu besprechenden Softwareinterrupts unterscheiden. Whrend beim Hardwareinterrupt die Interruptnummer von der Hardware, hier vom Interruptkontroller, geliefert wird, und auerdem der Zeitpunkt in keinerlei Zusammenhang zum gerade laufenden Programm steht, wird beim Softwareinterrupt die Interruptnummer vom Programm selbst geliefert, und der Zeitpunkt dazu wurde daher vom Programmierer, wie bei einem Unterprogrammaufruf festgelegt. 
Der Vorteil dieser Vorgangsweise das einfache Merken der Interruptnummern; die zugehrigen Programme knnen bei verschiedenen Programmversionen immer an anderer Stelle zu liegen kommen. Die Alternative, die Angabe einer konkreten Adresse, vervierfacht die zu merkende Nummer: Beispiel: Interrupt 09H -> Adresse 034H. Auch knnte man die Nummer fr zuknftige CPUs mit abweichender Adrestruktur beibehalten. 
Die Reaktion auf eine Unterbrechungsanforderung ist folgende: die CPU beendet den aktuellen Befehlszyklus (bei Wiederholungsbefehlen wird nicht auf das eigentliche Ende gewartet), speichert den Zustand der Flaggen am Stapel und daraufhin die aktuelle Adresse, bestehend aus Segment und Offset, und besttigt die Interruptanforderung mit INTA. Gleichzeitig verhindert die CPU weitere Interruptanforderungen durch Setzen des Interrupt-Flag IF. 
Darauf sendet der Interruptkontroller den Assemblerbefehl INTn, wobei n eine im Interruptkontroller whrend des Bootvorgangs gespeicherte Zahl, die Interruptnummer, ist. Der Prozessor holt den Interruptvektor(Segement+Offset) von der Adresse n*4 und setzt die Abarbeitung der Befehle dort fort. 
Jedes Interrupt-Service-Programm hat neben seiner eigentlichen Aufgabe die Verantwortung, den Zustand der CPU so herzustellen, wie er vorgefunden wurde; dazu kommt, da das Interruptprogramm kein Programm zum Verweilen ist, also keine Eingaben, im Prinzip auch keine Ausgaben und auch sonst keine Haltepunkte enthalten darf. Damit das ursprngliche Programm wieder fortgesetzt werden kann, mu diese Interrupt-Service-Routine mit dem Befehl IRET beendet werden. Interrupt-Service-Routinen sind im allgemeinen ein Fall fr Assemblerprogrammierung aber fr nicht allzuhufiges Auftreten kann man auch einmal in einer Hochsprache programmieren. 
Alle Interruptquellen, die am Interrupt-Kontroller enden, knnen individuell am Kontroller ausgeschaltet werden und auch alle gemeinsam mit dem Assemblerbefehl CLI, der in C der Funktion disable() entspricht, ausgeschaltet werden. Das Einschalten besorgt STI (oder enable() in C). 
Ein weiteres wichtiges Problem mu der Interruptkontroller noch lsen: Die Gleichzeitigkeit von Interrupt-Ereignissen mu er 'gerecht' beurteilen knnen. Das wichtigere Ereignis hat Vorrang. Diese Entscheidung wurde schon bei der Konstruktion des PC getroffen: Der Interrupt-Eingang IRQ-0 (TIMER) hat die hchste Prioritt, dann kommt die Tastatur, dann die Festplatte usw., d.h. je niedriger die Interruptnummer, desto hher die Prioritt. 
Nicht maskierbare Unterbrechung (NMI)
Der nichtmaskierbare Interrupt NMI hat die Aufgabe, auf schwerwiegende Fehler zu reagieren, insbesondere auf Speicherfehler, angezeigt durch einen Parittsfehler (9-tes Bit im RAM-Speicher) und beispielsweise auch einen Spannungsausfall, angezeigt durch eine Spannungszustandsanzeige des Netzteils. Der NMI kann durch keine Softwaremanahme abgeschaltet werden, kann aber, wie alle anderen Interrupts auch, zwecks Bearbeitung im eigenen Programm umgelenkt werden. 
Tabelle der Hardware-Interrupts
Name	XT/AT	Nummer	Adresse	BeschreibungNMI	X+A	2	8	Paritt *)IRQ0	X+A	8	20	Timer *)IRQ1	X+A	9	24	Tastatur *)IRQ2	X+A	A	28	reserviert(XT), Int.8-15(AT)IRQ3	X+A	B	2C	COM2 oder SDLCIRQ4	X+A	C	30	COM1 oder SDLCIRQ5	X+A	D	34	HD(XT), LPT(AT)IRQ6	X+A	E	38	Floppy DiskIRQ7	X+A	F	3C	LPTIRQ8	A	70	1C0	EchtzeituhrIRQ9	A	71	1C4	Umgelenkt auf IRQ2IRQ10	A	72	1C8	verfgbarIRQ11	A	73	1CC	verfgbarIRQ12	A	74	1D0	verfgbarIRQ13	A	75	1D4	KoprozessorIRQ14	A	76	1D8	FestplatteIRQ15	A	77	1DC	verfgbar*) am Busstecker nicht verfgbar
Diese Zusammenfassung der PC-Hardware ist eine Beschreibung des beim hardwarenahen Programmieren zu bercksichtigenden Umfeldes. Besonders die auf Kompatibilitt bedachte hardware-nahe Programmierung erfordert ein Verstndnis fr die Entwicklung der PC-Hardware und mu in der Lage sein, entweder auf XT-Niveau oder abhngig vom CPU-Typ zu arbeiten. Wir knnen uns vorstellen, da die Forderung nach Abwrtskompatibilitt also Arbeiten auf XT-Niveau, langsam und unkomfortabel sein kann, anderseits die zustzliche Forderung nach automatischer Erkennung einer bestimmten Hardwaresituation und jeweils Anpassung der Software sehr aufwendig und kodeintensiv sein kann. 
Wenn irgendein Detail hier noch zu kurz gekommen ist, wir werden bei unserem Software-Streifzug praktisch alle Einzelheiten noch einmal antreffen. 
Lit.: The XT-AT-Handbook, Choisser & Forster, 2. EditionThe programmer's Reference, Thom Hogan, Microsoft PressThe IBM-Technical Reference, TGM-DSK-140TGM-LIT-32: Datenbltter der ICs im PC
Einleitung
Die Sammlung der vorliegenden Progrmmchen ist Bestandteil des gleichnamigen Unterrichtsblocks in TINF in einer Abendschulklasse. Es wurden dabei zwei Ziele verfolgt: 1. Die Erweiterung der Programmierkenntnisse von PASCAL auf C. 2. Die Erarbeitung von Kenntnissen ber wichtige Hardwareeigenschaften unseres Computers mit den Mitteln dieser Sprache. Den Schlern waren zu Beginn dieses Unterrichtsblocks die grundlegenden Eigenschaften von C bekannt; sie haben diese Sprache an Hand des ADIM-Bandes 40 erlernt. 
Um den Schlern die Gelegenheit zu geben, das Gelernte in erweiterter Form nachlesen zu knnen, wurde diese Beitragsreihe zusammengestellt. Sie knnen alle Teile auch gesammelt unter TGM-LIT-xxx beziehen. Viele Teile dieser Reihe gehen ber die kurzen Beispiele aus dem Unterricht hinaus. 
Diese, auf den ersten Blick auch fr den Autor berraschend lang geratene, Einleitung soll zeigen, warum hardwarenahes Programmieren wichtig ist, wo es verwendet werden mu und warum es in Zukunft weniger wichtig werden wird. Wir werden bei der spteren Besprechung von Programmbeispielen immer wieder auf die Zusammenhnge aus der Einleitung zurckgreifen knnen. 
In gewisser Weise ist die Einleitung aber auch eine Zusammenfassung von grtenteils Bekanntem, wir sollten diesen Abschnitt daher besser bersicht nennen. 
Die Sprachen und Hardwarenhe
War PASCAL als Unterrichtssprache gedacht, so entstand C als Entwicklungswerkzeug fr UNIX. Vermutlich haben beide Schpfer den Erfolg ihrer Bemhungen nicht vorausgesehen. Aus den ursprnglichen Aufgabenstellungen kann aber doch darauf geschlossen werden, da sich C besonders gut in eine hardwarenahe Entwicklungsumgebung mit einem Assembler einfgt. Dennoch ist C eine Hochsprache, die uns standhaft den Zugriff auf Register der CPU verweigert und nur auf dem Umweg ber Variable erlaubt Speicher zu benutzen. 
Ist die CPU nicht auch eine Hardware-komponente? Wenn wir sie dazuzhlen, ist eigentlich nur ein Assembler wirklich geeignet hardwarenah zu agieren, denn in C ist uns der unmittelbare Zugriff auf die CPU-Register, auer in Ausnahmefllen, verwehrt. Wir mssen uns in C daher auf den Zugriff von Hardware-Registern auerhalb der CPU beschrnken. 
Teil 3: Sprache fr die hardwarenahe Programmierung

Einleitung
In der vorigen Folge haben wir uns einen berblick ber die PC-Hardware verschafft und werden nun daran gehen, die Hardware 'von innen' zu untersuchen. 'von innen' soll heien mit Softwarehilfen. Das Gegenstck 'von auen' hiee, mit Mitteln der Metechnik. Dafr whlen wir ein Werkzeug, eine Sprache. 
Die Sprachen und Hardwarenhe
Hochsprache oder Assembler?
Bis jetzt sprachen wir nur von den IO-Bausteinen. Ist die CPU nicht auch eine Hardwarekomponente? Wenn wir neben den Peripheriebausteinen auch die CPU beherrschen wollen, ist eigentlich nur ein Assembler wirklich geeignet, hardwarenah zu programmieren, denn in Hochsprachen ist uns der unmittelbare Zugriff auf die CPU-Register, auer in Ausnahmefllen, verwehrt. Wir mssen uns in Hochsprachen daher auf den Zugriff auf Hardware-Register auerhalb der CPU beschrnken. 
Anderseits knnten wir uns vorstellen, in derselben Hardwareumgebung verschiedene CPUs einzusetzen. Es gibt auch am PC solche Konzeptionen, die einen Austausch der CPU auf einer eigenen Einschubkarte vorsehen. So kann man eine bewhrte Hardware (PC) auf einem zuknftigen Prozessor (etwa auf einen P5 (=586) oder auf einer 68xx-CPU einsetzen. 
Die CPU ist daher nur eine Art austauschbarer Motor und jede Kodezeile, die nicht von der CPU-Kodierung abhngt, ist nicht unbedingt verlorene Arbeit. 
Assemblerprogrammierung ist ein eigener Problemkreis zu dessen Erarbeitung wir z.B. das Handbuch 'Maschinennahe Programmierung unter MSDOS' von Koll. Riemer empfehlen knnen. Es gibt natrlich Anwendungsflle, in denen Assemblerprogrammierung jedenfalls der Vorrang zu geben ist, wie z.B. bei speicherresidenten Erweiterungen des Betriebssystems, bei Gertetreibern oder bei Geschwindigkeitsoptimierungen aber die Mehrheit der Programme kommt mit einer Hochsprache als Werkzeug aus. Die Hochsprache bietet uns auch die Mglichkeit, das Problem an sich klarer zu erkennen, da die Architektur der CPU nicht wichtig ist. Daher wird das Erarbeitete eher von allgemeinerem Nutzen bleiben. 
PASCAL oder C?
Fr Unterrichtszwecke in Elektronik-HTLs werden vor allem PASCAL und C verwendet. War PASCAL als Unterrichtssprache gedacht, so entstand C als Entwicklungswerkzeug fr UNIX. Vermutlich haben die Schpfer beider Sprachen den Erfolg ihrer Bemhungen nicht vorausgesehen. Arbeitet man mit BORLAND-Produkten, verschwimmen die Unterschiede zwischen den Sprachen, da die mchtige Arbeitsumgebung beim Sprachenwechsel vieles bekannt erscheinen lt. Viele Argumente, die bei anderen Kompilern deutlich fr C sprechen, sind bei BORLAND weniger stichhaltig, etwa dieses: 
"C fgt sich besonders gut in eine hardwarenahe Entwicklungsumgebung mit einem Assembler ein". (Bei BORLAND findet man neben dem TASM auch einen eingebauten Assembler BASM, der Assemblerprogrammierung auf einfachste Weise im Hochsprachenmodul, gleichgltig, ob PASCAL oder C, ermglicht). 
oder
"In C kann der Speicher eleganter adressiert werden." (Bei TURBO-PASCAL findet man die Variablen mem und port, die das auch gut knnen). uvam.
Wichtig ist, da C eine derart groe Verbreitung besitzt, da kein Weg daran vorbei fhrt. C ist universell, da es sich vielen, auch ungewhnlichen Problemstellungen anpat, wie Sie auch in Folge 4 dieses Beitrags lesen knnen. Programme fr Mikrokontroller werden ebenso in C geschrieben, wie Programme fr Windows und die Unterrichtszeit ist zu kurz, um mehrere Sprachen zu lernen (und dann keine wirklich zu beherrschen). Keine andere Sprache hat derzeit eine derartige Anwendungsbreite. 
C bringt uns nher an die Hardware und ans System als PASCAL durch die Objektkodekompatibilitt mit Assemblerprogrammen. Wir knnen den gesamten Adreraum mit zweckmig whlbaren Speichermodellen ausnutzen. 
C bringt uns aber auch nher an die Anwendung als PASCAL, wenn mit den Mglichkeiten der Spracherweiterung C++ gearbeitet wird. Die Formulierungsstrke berladener Operatoren etwa, ist derzeit in PASCAL nicht zu haben. Da diese Spracherweiterungen auch fr hardwarenahes Programmieren hervorragend geeignet sind, knnen wir bereits in Folge4 zeigen. 
Fr PASCAL spricht, da es zum Erlernen von strukturiertem Programmieren konzipiert wurde. Da Lernen ein aufwendiger Proze ist, verzichten viele auf dieses bestehende Wissen nicht gerne und verwenden PASCAL, begnstigt durch den Komfort von TURBO-PASCAL, auch fr Anwendungen. Vorteil: Sie sind im sicheren Hafen einer strengeren Sprache als es C ist und laufen weniger Gefahr auf den Klippen von C Schiffbruch zu erleiden. 
Vergleicht man die Sprachen mit dem allseits geliebten Automobil, so wre PASCAL ein 'sicheres Gefhrt' und C der 'Rennwagen'; beide haben so ihre Vorzge. Sind HTL-Schler Sonntagsfahrer? Wollen wir eigentlich Rennfahrer? Wir wollen einfach gute Fahrer. Vielleicht ist C++ die Lsung. 
Ich habe den Eindruck, durch das Arbeiten mit C mehr ber den PC erfahren zu haben, als es in PASCAL der Fall war, wenn ich auch zugeben mu, da das MTTS ('mean time to success') fr Anfnger bei TURBO-PASCAL etwas krzer ist, bedingt hauptschlich durch den spten Einsatz von Pointern in PASCAL. Viele Schler kommen erst gar nicht in die Verlegenheit sich mit diesen maschinennahen Gebieten zu beschftigen, die geeignet wren den Zusammenhang zwischen ihrem Programm und der Hardware auf der sie luft aufzuzeigen, da der Unterricht gerade rechtzeitig durch ein Semesterende o.. keine Fortsetzung vorsieht. 
Welches C?
Anders als PASCAL bietet uns C die Mglichkeit aus einer groen Zahl verschiedener Kompiler auzuwhlen: TURBO/BORLAND, ZORTECH, MICROSOFT, QUICK, WATCOM, BDS und das sind nur die Bekanntesten. Die Wahl ist schwierig und auch nicht. 
Fr den Unterrichtsgebrauch ist BORLANDC wegen seiner hervorragenden Benutzungsoberflche vorzuziehen, obwohl derzeit nicht gerade fr groe Anwendungen geeignet. Auch beim HUGE-Modell ist bei 1MB Schlu. Da die Updates bei BORLAND etwa alle 6 Monate einlangen, werden wir wohl am Beginn des nchsten Jahres mit dem 32-bit-BORLAND-Kompiler rechnen knnen. 
Jemand, der es schtzt mit einer Vielzahl von Publikationen kompatibel zu sein, wird MICROSOFT vorziehen, obwohl MICROSOFT durch seine versptete Herausgabe des C++-Kompilers viele Freunde verloren hat und neue Anwendungen hufiger ZORTECHC oder BORLANDC verwenden. 
Soll ein Programm auf vielen verschiedenen Systemen laufen (OS/2, MSDOS, UNIX, APPLE), dann ist ZORTECHC die richtige Wahl. Auch die Speichermodelle gehen durch die Verwendung von DOS-Extendern ber die 1MB-Grenze hinaus. Die Benutzung des Kompilers ist etwa um einen Integrationsschritt hinter BORLANDC. Man ist 'nher an der Kommandozeilenversion', im Bezug auf die Speichermodelle aber einen Schritt weiter. 
Die BORLAND-Kompiler sind einander sehr hnlich, da sie gleichartige Benutzerfhrung enthalten. Ist man TURBO-PASCAL gewhnt, findet man die Sprachenfrage eher unnotwendig, da man kaum etwas in TURBO-PASCAL vermit, was man zum Erstellen eines Programms bentigt. Wenn jemand PASCAL sagt, meint er TURBO-PASCAL. Bei C ist das etwas anders, TURBO-C mu sich an einem vorhandenen Standard orientieren und sich Vergleichen mit gleichwertigen Fremdprodukten unterziehen. 
C ist portabel!
...aber nur, wenn man Formulierungen anwendet, die Portabilitt begnstigen. Bei unseren Programmierbeispielen wird auf diesen Aspekt besonderer Wert gelegt. 
Man mu mehrere Arten der Portabilitt unterschieden: Fr verschiedene Hardware (z.B.: PC - APPLE - PS/2), fr verschiedene Betriebssysteme (z.B.: MSDOS - SYSTEM7 - OS/2 - WINDOWS - UNIX), fr verschiedene Kompiler (BORLANDC - MICROSOFTC - ZORTECHC), fr verschiedene Speichermodelle (TINY-SMALL-MEDIUM-COMPACT-LARGE-HUGE) und Kombinationen aus diesen. 
Hardwareportabilitt
Hardwareportabilitt besteht, auer in Sonderfllen nicht. Man mu die Software so verfassen, da die hardwarebezogenen Teile mglichst in kleinen austauschbaren Modulen zu liegen kommen und der Rest maschinenunabhngig ist. 
Trotzdem ist fr den PC geschriebener Hardware-Kode in gewissen Grenzen auf anderen Gerten ablauffhig, wenn bei der Formulierung konkrete PC-Adressen vermieden werden und stattdessen Makros eingesetzt werden. Erleichtert wird diese Vorgangsweise mit der Datei IODEF.H, die man mit #include in Dateien einbindet, die IO-Zugriffe enthalten. Weitere Verwendungshinweise fr diese Headerdatei finden Sie in den Beispielen HC04TA1C.C und HC04TA1D.C. 
Um auch bitweise Ein- und Ausgabe zu ermglichen, wurden fr einige IO-Bausteine auch Bitadressen definiert. Ein Beispiel fr deren Anwednung ist HC04IO5.CPP. 
Betriebssystemportabilitt 
Betriebssystemportabilitt ist schon eher machbar, wenn sich die Programme auf der Grundlage des sogenannten ANSI-Standard bewegen. Die BORLAND-on-line-Hilfe ist hilfreich bei der Vermeidung inkompatibler Funktionen. Bei BORLANDC-3.0 ist bei jeder Funktion angegeben, ob sie unter DOS, WINDOWS, UNIX oder ANSI definiert ist. 
In der nchsten Zeit steht uns ein Wechsel auf ein 32-bit-Betriebssystem ins Haus. Davon werden besonders die Lngen der Variablen betroffen sein. Immer wenn es um die Gre von Variablen geht, setze man nicht die auf dem eigenen Kompiler zutreffende Zahl, sondern den sizeof()-Operator ein. Damit wird die bertragung auf andere Betriebssysteme erleichtert, die mit einer anderen Wortlnge arbeiten. 
statt   memcpy(d,s,2*n)    besser    memcpy(d,s,sizeof(int)*n)
Kompilerportabilitt 
Kompilerportabilitt bedeutet, da ein Programm ohne nderung auf verschiedenen Kompilern bersetzbar ist. Fr einfache ANSI-konforme Probleme ist das keine Schwierigkeit. Man kommt aber bald drauf, da die schnsten Funktionen praktisch nicht verwendet werden knnen, will man auch einmal einen anderen Kompiler probieren. Tut man es doch (verwendet man also Textausgabefunktionen mit BIOS-Steuerung, wie in conio.h bei BORLAND, Grafikfunktionen, Klassenbibliotheken usw.) begibt man sich in totale Abhngigkeit von einem Hersteller. 
Alle Kompiler kennen vordefinierte Makros, die es erlauben, beim Kompilieren festzustellen, welcher Kompiler gerade arbeitet. 
Makro       Kompiler                         _MSC_VER    Microsoft C ab Version 6.0_QC         Microsoft Quick C ab Version 2.51__TURBOC__  Borland Turbo C und Turbo C++__ZTC__     Zortech C und C++__WATCOMC__ WATCOM C
Der Autor verwendete bis vor kurzem daher Konstruktionen wie diese:
#ifdef __TURBOC__ ...#endif#ifdef __MSC_VER ...#endif
Die durch #ifdef...#endif-Zeilen eingeschlossenen Kodeteile werden durch jenen Kompiler bersetzt, fr den das entsprechende Makro definiert ist oder eben ignoriert. 
Wenn man auch bei komplizierteren Dingen um die bedingte Kompilierung nicht herumkommt (ein durchaus bersichtlicher Kode kann durch solche Einfgungen ganz schn zerhackt werden), viele Kleinigkeiten lassen sich durch Header-Datei portable.h aus dem Weg rumen. Angelehnt an: /Scott Robert Ladd, "Portability Across MS-DOS C Compilers", The C Users Journal, Jan.1991, S.53..56/. Die dort beschriebene Datei port.h wurde erweitert und thematisch aufgeteilt. Sie findet in vielen der folgenden Programme ihre Anwendung. 
Kompatibilitt zu K&R-C-Kompilern errreicht man durch Verwendung des Makros __STDC__, welches, wenn gesetzt, anzeigt, da der Kompiler im ANSI-Modus arbeitet. 
#ifdef __STDC__  int func (int i)#else  int func1();#endif
Headerdatei portable.h
Gerade im nicht-genormten, hardwarenahen Bereich gibt es groe Unterschiede zwischen den Kompilern. Bei einem Wechsel des Kompilers mssen viele Zeilen mit #if..#endif-Konstruktionen angepat werden. Einiges davon lt sich durch geeignete Header-Dateien beseitigen. 
IO-Zugriff in C als Beispiel
Jede hhere Sprache enthlt eine Entsprechung fr die IO-Befehle, so auch C. Bei TURBO-C(BORLAND-C) gibt es die Funktionen inport() und outport(). 
Assembler	TURBO-CIN AL,adr	unsigned char inportb(unsigned int adr);IN AL,DXIN AX,adr	int inport(unsigned int adr);IN AX,DXOUT adr,AL	outportb(unsigned int adr, unsigned char c);OUT DX,ALOUT adr,AX	outportb(unsigned int adr, int c);OUT DX,AX
Es gibt jeweils fr 2 Assemblerbefehle einen C-Befehl. Das liegt daran, da man beim Assembler im Adrebereich 0..255 (0x00..0xff) direkt oder registerDX-indirekt adressieren kann und ber der Adresse 255 nur ber das DX-Register. In C wird da kein Unterschied gemacht, es liegt am Kompiler, ob er die eine oder andere Adressierungsart verwendet oder, ob er immer registerDX-indirekt adressiert. 
Bei Microsoft heien diese Funktionen aber inp() und outp() Damit wir kompilerunabhngig arbeiten knnen, empfiehlt es sich, selbstdefinierte Makros zu verwenden, die in der Header-Datei portable.h je nach Kompiler entsprechend expandieren. In unserem Fall unterscheidet sich TURBOC vom Rest der C-Kompiler. TURBOC verwendet inportb(), die anderen Kompiler inp(). Das Makro IN_PORT substituiert, je nach Kompiler die richtige Funktionsbezeichnung. 
#ifdef P_IO/*----------------------------------------------------    I/O Port Macros    IN_PORT     read  byte from I/O port    IN_PORTW    read  word from I/O port    OUT_PORT    write byte  to  I/O port    OUT_PORTW   write word  to  I/O port----------------------------------------------------*/#if defined(__TURBOC__)    #ifndef __DOS_H      #include <dos.h>    #endif    #define IN_PORT(port)       inportb(port)    #define IN_PORTW(port)      inport(port)    #define OUT_PORT(port,val)  outportb(port,val)    #define OUT_PORTW(port,val) outport(port,val)#else    #ifndef __CONIO_H      #include <conio.h>    #endif    #define IN_PORT(port)       inp(port)    #define IN_PORTW(port)      inpw(port)    #define OUT_PORT(port,val)  outp(port,val)    #define OUT_PORTW(port,val) outpw(port,val)#endif#endif
ber dieses Beispiel hinaus enthlt die Datei portable.h viele andere Details, die die bedingte Kompilierung im Programm oft entbehrlich macht. Um die Kompilierzeit nicht unntig zu erhhen, wird bei der Inklusion von portable.h die ganze Headerdatei kompiliert, wurde aber vorher zumindest ein Makro P_... (hier P_IO) definiert, wird nur der betreffende Teil bersetzt. Dieser Mechanismus wird dann bei den betreffenden Beispielen noch einmal gezeigt. 
Speichermodellportabilitt 
Speichermodellportabilitt lernen wir am besten beim Lesen der Include-Dateien des Kompilers. 
Einige Fragen zu C
Dieser Abschnitt versucht die Beantwortung hufig gestellter Fragen, die aus der Sicht einer mehrjhrigen Arbeit mit einigen Kompilern mglich geworden ist. 
Was ist Standard-C?
Im Prinzip gibt es einige maschinenunabhngige C-'quasi'-Standards, davon ist einer eine wirkliche Norm (ANSI) mit grter Verbreitung: 
(1) K&R-Standard (Kerninghan & Ritchie) 	(2) UNIX-Standard	 (3) ANSI-Standard. X.3159	(4) MICROSOFT-Standard	 (5) AT&T - C++ Ver. 2.1, als ANSI in Vorbereitung
Alle namhaften Kompiler fr den PC verarbeiten Programme nach diesen Standards. 
(1)K&R ist die lteste Sprachbeschreibung, siehe etwa in: /Kernighan, Ritchie, Programmieren in C, Carl Hanser, Mnchen Wien, 1983/. Der K&R-Standard wird zwar in neuen Programmen nicht mehr angewendet, man findet aber immer noch Programme, die danach formuliert sind, so auch manches Mikro-Kontroller Programm. Wichtige Unterschiede zu ANSI C:
	K&R	ANSI(a)	main()	int main(void)			void main(void)	(b)	char fkt();	char fkt(int i, char c);	(c)	char fkt(i,c)	char fkt(int i, char c)		int i;	{...	char c;		{...
(a)	Der Rckgabewert von Funktionen wurde bei K&R immer als eine ganze Zahl angenommen, da dieser Fall, etwa zur Fehlerrckmeldung, der hufigste war. Daher mute links vom Funktionsnamen kein Rckgabetyp stehen. Aus Kompatibilittsgrnden ist diese Schreibweise auch heute noch erlaubt, jedoch zu vermeiden. Wenn kein Parameter zurckgeliefert oder bergeben werden soll, ist der Typ void zu verwenden. 
(b)	Prototypen enthalten bei K&R keine Parameter. Sie sind eigentlich nur notwendig, um dem Kompiler externe Funktionen bekanntzugeben. Sie hatten keine Typenprffunktion. (Das hat man erst von PASCAL gelernt.) 
	Die Anzahl der Parameter konnte dem Kompiler deshalb egal sein, da in C, im Gegensatz zu PASCAL, der rufende Programmteil fr die Wiederherstellung des Stapels verantwortlich ist. Daher besteht keine unbedingte Notwendigkeit, die Anzahl der bergabeparameter aufeinander abzustimmen. In C sind daher Unterprogramme mit einer variablen Anzahl von Parametern kein Problem. 
(c)	Die Schreibweise der bergabeparameter war bei K&R noch anders und sollte in dieser Form ebenfalls nicht mehr verwendet werden. 
Es gibt noch andere Unterschiede; die hier beschriebenen sind aber die Aufflligsten. 
(2) UNIX ist nicht unser unmittelbares Anliegen, wer aber UNIX-kompatibel programmieren mu, verwende nur UNIX-kompatible Funktionen, wie in der on-line-Hilfe angegeben. 
(3)ANSI ist die breiteste transportable Plattform, die eine bertragung auf andere Kompiler und Rechner gewhrleistet. Schaltet man einen Kompiler in den ANSI-Modus, 'vergit' dieser alle CPU-typischen Besonderheiten, wie near, far..
Hardwarebezogene Funktionen kommen in der ANSI-Definition nicht vor, daher knnen wir bei unserem Vorhaben nicht allein diese Sprachkonstruktion benutzen. 
Zwar schaltet der Kompiler zustzliche Schlsselwrter aus, aber alle Zusatzfunktionen des Kompilerherstellers (, die auch nicht ANSI-konform sind,) knnen verwendet werden. Diesem Punkt ist bei portablen Programmen besonderer Augenmerk zu widmen. Will man so geschriebene ANSI-Programme auf einen anderen Kompiler bertragen, wird man wahrscheinlich zu grberen Eingriffen gentigt sein. Lsung 1: Vermeidung von Funktionen, die nur bei einem Kompilerhersteller erhltlich sind. Lsung 2: Kauf der Runtime-Library im Source-Kode und Aufbau einer eigenen portierbaren Bibliothek. Lsung 3: Im einfachen Fllen knnen Makros bergnge erleichtern (wird spter gezeigt). 
(4)MICROSOFT-C ist ein Quasi-Standard fr den PC. Den ANSI-Kompiler gibt es nicht. Es gibt nur Kompiler fr verschiedene Betriebssysteme/CPUs, mit einer ANSI-Einstellung. Am PC war MICROSOFT viele Jahre tonangebend, auch weil BORLAND die Sprache C erst nach PASCAL auf ein entsprechend konkurrenzfhiges Niveau gebracht hat. MICROSOFT hat zuerst die fr die 80x86-CPU typischen zustzlichen Begriffe in C eingefhrt. Ob man es will oder nicht: wir mssen uns (und auch BORLAND mu sich) in vielen Belangen nach dem Marktfhrer richten. MICROSOFT hat beginnend mit der Version 3 seines C-Kompilers einen quasi-Standard fr ein C auf dem PC festgelegt. Viele Schlsselwrter und Makros, die nur fr die 80x86-CPUs gelten, wurden bei MICROSOFT zuerst benannt und von den anderen Herstellern oft bernommen. Da die bernahme nicht immer 1:1 erfolgt, liegt an der Schwierigkeit, nicht des Plagiats bezichtigt zu werden. Daher unterscheiden sich die Kompiler oft in entscheidenden Punkten. Einige dieser Unterschiede werden wir durch eigene MAKROS auszugleichen haben. 
BORLAND-Kompiler (BORLANDC und TURBOC) erlauben die Programmierung in allen angefhrten Varianten durch entsprechende Schalter in der Programmierumgebung. Aber Achtung: Es werden zwar alle unerlaubten Schlsselwrter ausgeblendet (etwa gibt es in ANSI-C kein far oder near usw.), es bleiben aber alle Bibliotheksfunktionen, auch alle von BORLAND erfundenen, verwendbar, ob sie nun bei ANSI definiert sind oder nicht. Daher mu sich der Programmierer an Hand der on-line-Hilfe selbst berzeugen, ob eine bestimmte Funktion anwendbar ist oder nicht. Beim neuen BORLANDC-3.0 ist die Anwendbarkeit der Funktionen in den verschiedenen Standards besonders deutlich hervorgehoben. 
(5)Die geringste Auseinanderentwicklung von Sprachmerkmalen gibt es bei der C++-Version. Die Kompilerhersteller hinken mit ihren Implementationen dem ATT-Standard eher nach, was die Einheitlichkeit begnstigt, da die Norm vor den Produkten bekannt ist. 
C oder C++ fr den Programmierer
Neu geschriebene Programme mssen keineswegs mit dem geringen Umfang des ANSI-Standards geschrieben werden, man kann ruhig alle Mglichkeiten, die sich durch die Kompilerung mit 'C++ always' ergeben verwenden. Dies umso sicherer, als auch MICROSOFT als einer der letzten Hersteller mit der Version 7 den ATT-Standard fr C++ bernommen hat. Welche Mglichkeiten das im besonderen sind, finden Sie auf Seite 45 der PC-NEWS 2/91 und auf den Seiten 63 und 64 der PC-NEWS 3/91. Dabei ist von Klassen und anderen Begriffen der objektorientierten Programmierung noch keine Rede. Nur die Wartung von 'altem' Kode erfordert Rckgriffe auf die lteren Sprachversionen. 
C oder C++ fr den Unterricht?
Geht es darum, mglichst klar, Programmiertechniken, wie strukturiertes oder objektorientiertes Programmieren zu vermitteln, ohne Rcksicht auf bestehende Software: hier ist C++ anzuraten. Siehe dazu Beitrag "C++ im Unterricht, Ein Beitrag zur Sprachendiskussion", M.Weissenbck, PC-NEWS 2/91, S.49..51. Dazu gibts den ADIM-Band 50 als Schulbuch. 
Wir mssen in einer Umwelt leben, die mit vielen herkmmlich formulierten C-Programmen (z.B. K&R-Niveau) 'belastet' ist. Ein Schler, der an Hand von ADIM-Band-50 strukturiert und objektorientiert in C++ formulieren lernt, verliert vielleicht den Zusammenhang zu dem, was ihn in der rauhen Wirklichkeit bescheidenerer C-Kompiler erwartet. Er ahnt nicht, was alles bei einfacheren Kompilern nicht geht. C ist eine 'Breitbandsprache' und daher sowohl bei sehr komplexen als auch bei sehr einfachen Anwendungen, wie etwa bei Mikro-Kontrollern weit verbreitet. Alle Elektroniker mssen daher auch diesen Aspekt bercksichtigen. Fr alle, denen es wichtig ist zu wissen, wo die Trennlinie zwischen C und C++ verluft, kann daher empfohlen werden, zuerst an Hand von ADIM-Band-40 Standard-C, und danach erst an Hand von ADIM-Band-50 C++ zu lernen. 
Welche C-Version?
Die Neueste natrlich! Aber genauso natrlich ist das auch nicht immer die richtige Antwort, denn wenn Sie nicht mindestens 2MB Hauptspeicher und nicht mindestens einen AT besitzen, luft der BORLAND-Kompiler in der Version 3.0 nicht! Es ist aber eine Freude, dieses Programm zu verwenden! Die on-line-Hilfe macht Handbcher praktisch entbehrlich, wenn man nur etwas mit dem Aufbau von C vertraut ist. Die Version 3.0 bietet auch einem Anfnger optimale Hilfestellung. 
Kompilereinstellung
Praktisch jeder Kompilerhersteller liefert zwei Kompiler: einen fr C, den anderen fr C++ (oder einen Kompiler, mit einer entsprechend umschaltbaren Option). Welchen soll man verwenden? Beide Kompiler sind in der Lage, sauber geschriebene C-Programme zu bersetzen! 
Wie schaltet man etwa den BORLAND-Kompiler von C auf C++ um?
Im Punkt OPTIONEN des Hauptmens findet sich bei der Kompilereinstellung der Schalter 'C++ always' und 'C++ with extension CPP only' oder so hnlich. In der letzteren Stellung kompiliert er eine Datei NAME.C mit dem C-Kompiler und eine Datei NAME.CPP mit dem C++-Kompiler. Das wre der Normalfall, das heit die Datei-Endung bestimmt, welcher Kompiler verwendet wird. Andernfalls (C++ always) wird immer der C++-Kompiler verwendet. 
Ich selbst verwende die Dateiendung zur Umschaltung zwischen C- und C++-Kompiler. Man erkennt daher sofort, ob ein Programm C++-Elemente enthlt. Ich schreibe reine C-Programme nur mehr in Ausnahmefllen und benutze die CPP-Endung auch fr einfache Flle und zwar aus folgenden Grnden: (1) Der C++-Kompiler besitzt eine strengere Prfung der Syntax und ist daher weniger tolerant gegenber Fehlern. Um diesen Effekt zu optimieren, ist es auch empfehlenswert, speziell im Unterricht, alle Warnungen einzuschalten. (Im umgekehrten Fall: 'alle Warnungen aus' bleiben viele Fehler unerkannt). (2) Da ich die wichtigsten Unterschiede zwischen C- und C++-Verhalten des Kompilers schon kenne, benutze ich die neuen Mglichkeiten von C++ auch dann, wenn nicht gleich mit Klassen gearbeitet wird. Ob diese Mglichkeiten auch im Unterricht eingesetzt werden sollen, hngt auch damit zusammen, ob man etwa reines ANSI-C zeigen will oder, ob diese Unterscheidungen nicht wichtig erscheinen. 
BORLANDC oder TURBOC?
BORLAND unterscheidet zwischen professionellen Anwendern und 'Hobbyanwendern'. Fr die Ersteren gibt es BORLANDC und fr die Hobbyisten die abgemagerte Version TURBOC. In der integrierten Entwicklungsumgebung von TURBOC sind einige Einstellungen nicht vorhanden. Man knnte meinen, da fr den Unterrichtsgebrauch TURBOC gengt, leider fehlen aber bei TURBOC einige praktische on-line-Hilfen und einige Mglichkeiten beim Debuggen, soda es ratsam erscheint, auch beim Unterricht BORLANDC zu verwenden. Die bersetzten EXE-Dateien sind aber gleich. 
Vollversion oder Schulversion
Schulversionen besitzen laut Auskunft bei Firma BORLAND denselben Leistungsumfang wie die gleichnamige Vollversion. Allerdings gibt es bei Schulversionen kein Update-Service. Auerdem enthalten die Schulversionen einen Hinweis, da ihre Verwendung ausschlielich fr den Unterrichtsgebrauch bestimmt ist. Der Preis fr ein Update ist etwa so hoch, wie der Preis der Schulversion (Beispiel: Ver.2.0 auf Ver.3.0, BORLANDC DM 200,-, Ver.3.0 auf Ver.3.1 DM 99,-). Der Wermutstropfen der Vollversion ist allerdings der Einstiegspreis von ca. DM 1000,-. Preise in S werden erst bei vollstndigem Betrieb der sterreichischen Vertretung, etwa ab Oktober 1992 vorliegen (BORLAND, Modecenterstrae14, 1030Wien, (0222)-79-83-411, FAX:DW 33.)
Welchen Stellenwert hat Dokumentation?
Gemeint ist die Dokumentation des Programmkodes. Ein Anfnger, dem viele grundlegende Regeln einer Sprache noch fremd sind, wird von sich aus das Bedrfnis haben, mehr zu kommentieren (oder mehr kommentiert haben wollen) als der fortgeschrittene Programmierer. Die Dokumentation von Programmzeilen dient dem Anfnger als eine Art Lernproze; das Dokumentieren hilft ihm, Kodezeilen oder -abschnitte richtig zu interpretieren. 
Programme sollten aber so abgefat sein, da sie durch die Wahl geeigneter Bezeichnungen in sich verstndlich sind und nur an wirklich besonderen Stellen zustzliche Erklrungen enthalten sollen. Der Stil betrifft vor allem die richtige Bezeichnung von Variablen, Typen und Funktionen (bei C++ der Klassen und Objekte). Das betrifft sowohl die Schreibweise (Gro/Klein), als auch die Bedeutung der gewhlten Wrter. Sparen wir nicht an Zeit, eine aussagekrftige Bezeichnung fr die Variablen- und Funktionsnamen zu suchen! 
Und bedenken wir, da der Kompiler keine Prfung der Richtigkeit von Kommentaren bietet und daher nur allzu oft alter, nicht mehr zutreffender Kommentar stehen bleibt und den Leser eher verwirrt. 
Unterstrich kontra groe Anfangsbuchstaben
Bei der Formulierung komplexerer Typen oder Variablen haben sich zwei Varianten eingebrgert:
mit Unterstrichen:  	draw_polygon() oder	mit groen Anfangsbuchstaben: 	DrawPolygon()
In letzter Zeit bemerke ich, da viele amerikanische Autoren zur letzteren Schreibweise zurckkehren und damit die deutsche Groschreibung am Wortanfang entdecken. 
Der Unterstrich sollte sparsam verwendet werden, da er in C eine Funktion innerhalb der Stellung von Variablen und Unterprogrammen und Makros hat. Insbesondere sollten doppelte Unterstriche innerhalb von Bezeichnern vermieden werden, da diese beim 'mangling' von Namen in C++-Programmen verwendet werden ('mangling' ist die Vereinigung der vom Programmierer gewhlten Bezeichnung fr eine Funktion, deren Klassenzugehrigkeit und der bergebenen Parameter in einem neuen Begiff, der zum Linken verwendet wird. Daher ist es mglich, gleichnamige Funktionen zu verwenden, wenn sie sich nur in der Art oder Anzahl der bergebenen Parameter unterscheiden). 
Kompilerbedienung deutsch oder englisch?
Gemeint ist die Sprache der integrierten Entwicklungsumgebung und der on-line-Hilfe. Da wir im Unterricht BORLAND-Kompiler verwenden, knnen wir diese Frage stellen. Andere Kompilerhersteller hten sich, so komplexe Materie einem unklaren bersetzungsvorgang zu unterziehen. BORLAND-Deutschland hat der Verbreitung der BORLAND-Produkte einen guten Dienst erwiesen, diese bersetzung durchgefhrt zu haben; ob die bersetzung der Klarheit dienlich ist, und ob wir unseren Schlern einen guten Dienst erweisen, wenn wir ihnen die deutsche Version vorsetzen, ist eine andere Frage. In der englischen Version ist nmlich ein groes Lernpotential fr Sprachverstndnis fr technisches Englisch (hier Software-Englisch) enthalten, dessen wir verlustig werden, wenn wir mit der deutschen Version arbeiten. Gerade das Unbehagen, das die Schler bei Kontakten mit englischsprachigen Produkten haben, zeigt, da es auf diesem Gebiet noch viel zu lernen gbe. Denn die Realitt ist, da die wesentlichen Programmierunterlagen immer noch in englischer Sprache anfallen und wir daher den Umgang mit englischsprachigen Produkten frdern sollten. (Englisch, das Latein der EDV?) Bis zur Version 2 des BORLAND-Kompilers benutzte ich die ins Deutsche bersetzte Version des Kompilers, bin aber aus diesen Grnden jetzt auf die englische Version umgestiegen. (Anders ist es vielleicht bei Texteditoren, die auf eine deutsche Trennhilfe angewiesen sind.) 
Programmierstil deusch oder englisch?
Wie ist es mit der Sprachwahl bei den selbstgewhlten Bezeichnern? Sollen wir diese in Deutsch oder in Englisch verfassen? Programmierer in greren Firmen werden hier wohl wenig Wahl haben, sie werden sich allgemeinen Firmenregeln zu unterwerfen haben. Programme fr Schler werden in erster Linie in deutscher Sprache kommentiert werden mssen, ntzliche Programme auch wegen ihrer allgemeineren Austauschbarkeit ber Mailboxen in englischer Sprache. In den folgenden Beitrgen wird diese Unterscheidung ebenfalls getroffen: Beispielprogramme eher in Deutsch, lauffhige Programme eher in Englisch. 
Vorschau auf Folge 4, Hardwareprogrammierung: Wiederverwertbarer Kode * IO-Zugriff in C * Polling Tastatur, Maus, Timer * Programmierstil * Allgemeine Ein- Ausgabe, IO-Klassen IO.HPP, IO.CPP * 'Bitprozessor' * Demonstration der BYTE-Klassen * Testen der BYTE-Klassen * Demonstration der BIT-Klassen * Testen der BIT-Klassen * Anzeige und nderung des Zustandes von IO-Ports * Testen der parallelen Schnittstelle * Testen der seriellen Schnittstelle * Anwendung: CMOS-RAM * CMOS-RAM im Detail
Teil 4: Hardwareprogrammierung
Wir werden versuchen niederwertigste Aktionen (IN- und OUT-Befehle) mit hchstwertigen Softwaremethoden (Kapselung, Polymorphie, Vererbung) in C++ zu verbinden. 
Damit entsteht zwischen Hardware und Anwednung eine Zwischenschicht, die verhindert, da in eine Programm von mehr als einer Instanz auf einen Hardware-Port zugegriffen wird. 

Leser, die sich mehr fr die Anwendung als das 'Wie' interessieren, seien die Programme HC04PA1.EXE, HC04SE1.EXE, HC04CM1.EXE empfohlen. 

Gegenstand der Hardwareprogrammierung ist die Kommunikation mit den Ports der Peripheriebausteine. Wir knnen uns natrlich nur wichtige Beispiele ansehen; dabei wollen wir aber eine solide Basis in Form einer IO-Klasse entwickeln. Mglichst viele der Beispielprogramme sollen als wiederverwendbarer Kode in einer Bibliothek MYLIB.LIB zusammengefat werden. Diese Bibliothek soll mit jeder neuen Folge unserer Programmierbungen wachsen. 
Zum Titel
Nach unserer selbstgewhlten Definition verstehen wir unter Hardwareprogrammierung direkte Zugriffe auf die Register der Hardwarebausteine. Eine bersicht ber die Aufteilung des IO-Bereichs kennen wir schon aus den PC-NEWS 27, S.32. Jetzt, beim Programmieren werden wir genauere Angaben ber die eine oder andere Adresse bentigen. Hilfreich sind da die Beschreibungen der IO-Bausteine, siehe TGM-LIT-32. 
Die Hardwareprogrammierung am Baustein hat nur in Ausnahmefllen eine Berechtigung, wobei wir auf den ersten besonders Bezug nehmen wollen:
a.	zum Lernen	
b.	Entwicklung von Treibern	
c.	Erschlieung von Mglichkeiten, die auf hheren Ebenen nicht verfgbar sind. 	
d.	zu Testen
Mit Rcksicht auf Punkt a) werden wir auch einige Programme erproben, fr die auf hheren Ebenen Ersatz vorhanden wre. 
Die direkte Kommunikation mit Hardware-Ports ist auch bei Windows-Programmierung nicht grundstzlich unmglich - es ist nur praktisch nicht anwendbar - , da sich Windows vorbehlt zu wissen, welchen Zustand der IO-Port oder die Hardware allgemein gerade hat. 
Programmbersicht
In der folgenden Dateienbersicht sieht man die Beispielprogramme (HC04*.*). Whrend das erste vorgestellte Beispielprogramm HC04TA1*.* in vielen Varianten gezeigt wird, werden spter die Beispielprogramme jeweils in der Art von HC04TA1G abgebildet. Auf den zugehrigen Disketten finden Sie aber oft auch eine Variante in Standard-C. 
  c:\mylib  dok           dieser Text  lib           mit jeder Folge wachsende Bibliothek  include       alle Headerdateien  source        Quellkode  sample        Beispielprogramme HC*.*
c:\mylib\lib\mylib.lib        Bibliothekmylib.prj        Projektdatei fr mylib.libc:\mylib\include\ansi.h           portable Bildschirmsteuerung in Cansi.hpp         portable Bildschirmsteuerung in C++bit.h            Bitprozessorcmosram.hpp      CMOSRAM-Klasseform.h           formatierte Ausgabeheap.hpp         erleichtert das Finden von alloc-Fehlernio.hpp           IO-Klasseniodef.doc        Dokumentation zu iodef.hiodef.h          Definition von IO-Adressen im PCkeyscan.h        Alle SCAN-Kodes am PCmylib.h          alle Funktionenmytypes.h        INT, CHAR...portable.h       erleichtert das Portieren von:  P_CONSOLE        Konsolenfunktionen  P_FILE           DOS-Funktionen  P_IO             IO-Ansprache  P_MEM            Speicherzugriffe  P_POINTER        MP_FP,LCODE,LDATA,NCODE,NDATA  P_SOUND          Tonausgabe  P_STREAMS        Streams  P_TIME           Zeitabfragec:\mylib\sourcecmosram.cpp      Funktionen der CMOSRAM-Klassefil.c            filesize()form.cpp         dec(), hex(), chr(), form()io.cpp           Funktionen aus IO-Klassenis.cpp           ishex()kbd.c            kbd_read(), clear_kbdqueue()mou.c            mouse_read()mytypes.cpp      UCHAR, UINT, BOOL als Klasseport_bc.c        in BORLANDC nicht enthaltene Funktionenstr.c            putstr(), convert_string()tim.c            tim0_read(), u.a.c:\mylib\sample\general\heap_.cpp        testet alloc-Funktionenmytypes_.c       testet Typen UCHAR, UINT, BOOL c:\MYLIB\SAMPLE\hc04.prj         kompiliert alle HC04*.*-Dateienhc04bi1.cpp      testen des Bitprozessorshc04cm0.cpp      Beispieh fr die Ansprache des CMOSRAMhc04cm1.cpp      Lesen/Setzen des CMOS-RAM (C-Version)hc04cm1.exe      Lauffhige Version von hc04cm1.cpphc04cm1a.cpp     Lesen/Setzen des CMOS-RAM (C++-Version)hc04cm1a.exe     Lauffhige Version von hc04cm1a.cpphc04io0.cpp      Demo fr Grundfunktionen der IO-Klassehc04io1.cpp      Testen der Klassen IOBYTE_IN, -OUT, -IOhc04io2.cpp      Anzeige/nderung von IO-Portshc04io2.exe      Lauffhige Version von hc04io2.cpphc04io3.cpp      Testen der Klassen IOBIT_IN, -OUT, -IOhc04io4.cpp      Demo fr die Bearbeitung von Bitshc04io5.cpp      Ausschalten der Tastatur am XThc04mo1.cpp      Polling des Maus-Portshc04pa1.cpp      Testen der parallelen Schnittstellehc04pa1.exe      Lauffhige Version von hc04pa1.cpphc04se1.cpp      Testen der seriellen Schnittstellehc04se1.exe      Lauffhige Version von hc04se1.cpphc04ta1a.c       Polling der Tastatur, BORLAND-Versionhc04ta1b.c        -"- , ANSI-Versionhc04ta1c.c        -"- , MAKRO-Versionhc04ta1d.cpp      -"- , CPP-Versionhc04ta1e.cpp      -"- , Funktions-Versionhc04ta1f.cpp      -"- , Bibliotheksversionhc04ta1g.cpp      -"- , IO-Klassen-Versionhc04ti0.cpp      Polling des Timers
Headerdateien port*.H
Gerade im nicht-genormten, hardwarenahen Bereich gibt es groe Unterschiede zwischen den Kompilern. Bei einem Wechsel des Kompilers mssen viele Zeilen mit #if..#endif-Konstruktionen angepat werden. Einiges davon lt sich durch geeignete Header-Dateien beseitigen. Weiter hinten wird die Datei portio.h vorgestellt, die anderen dann in spteren Ausgaben jeweils nach Bedarf. 
IO-Zugriff in C
Jede hhere Sprache enthlt eine Entsprechung fr die IO-Befehle, so auch C. Bei TURBO-C(BORLAND-C) gibt es die Funktionen inport() und outport(). 
Assembler	TURBO-CIN AL,adr	unsigned char inportb(unsigned int adr);IN AL,DXIN AX,adr	int inport(unsigned int adr);IN AX,DXOUT adr,AL	outportb(unsigned int adr, unsigned char c);OUT DX,ALOUT adr,AX	outport(unsigned int adr, int c);OUT DX,AX
Es gibt jeweils fr 2 Assemblerbefehle einen C-Befehl. Das liegt daran, da man im Adrebereich 0..255 (0x00..0xff) direkt oder registerDX-indirekt adressieren kann und ber der Adresse 255 nur ber das DX-Register. 
Bei Microsoft heien diese Funktionen aber inp() und outp()1 Damit wir kompilerunabhngig arbeiten knnen, empfiehlt es sich, selbstdefinierte Makros zu verwenden, die in der Header-Datei portio.h je nach Kompiler entsprechend expandieren. In unserem Fall unterscheidet sich TURBOC vom Rest der C-Kompiler. TURBOC verwendet inportb(), die anderen Kompiler inp(). Das Makro IN_PORT substituiert, je nach Kompiler die richtige Funktionsbezeichnung. 
/*----------------------------------------------------    I/O Port Macros    IN_PORT     read  byte from I/O port    IN_PORTW    read  word from I/O port    OUT_PORT    write byte  to  I/O port    OUT_PORTW   write word  to  I/O port----------------------------------------------------*/#if defined(__TURBOC__)    #ifndef __DOS_H      #include <dos.h>    #endif    #define IN_PORT(port)       inportb(port)    #define IN_PORTW(port)      inport(port)    #define OUT_PORT(port,val)  outportb(port,val)    #define OUT_PORTW(port,val) outport(port,val)#else    #ifndef __CONIO_H      #include <conio.h>    #endif    #define IN_PORT(port)       inp(port)    #define IN_PORTW(port)      inpw(port)    #define OUT_PORT(port,val)  outp(port,val)    #define OUT_PORTW(port,val) outpw(port,val)#endif
An dieser Stelle knnte man sich fragen, ob die Art Ports anzusprechen, wie es die 80x86-Familie tut, wnschenswert ist oder ob man nicht grundstzlich memory-mapped IO, wie bei den Motorola-CPUs vorziehen sollte. Auch die Konstruktion von IO-Bausteinen, besonders der lteren Baureihen unterliegen keiner einheitlichen Konzeption, soda die nachfolgende Software viele Konstruktionsmngel der Chips ausgleichen mu, in vielen Fllen aber diese Mngel nicht mehr ausgleichen kann. 
Nachteile des IO-Konzepts der 80x86 CPUs 
*	Beschrnkung des Adreraumes (sowohl CPU- als auch Konstruktionsbedingt)
*	Eingeschrnkte Zugriffsmglichkeiten ber IN und OUT
*	Bruch des 'Freizgigkeitsprinzips', nach dem in Neumann-Architekturen Daten und Programme in demselben Speicherraum koexistieren.  
Vorteile
*	Der IO-Speicher kann mit Wartezyklen betrieben werden ohne, da der Hauptspeicherzugriff darunter leidet. 
*	Ob ein Programm eine Ein-/Ausgabe durchfhrt ist mnemonisch erkennbar. 
Besonderheiten herkmmlicher Chipkonzeptionen
*	Nicht rcklesbare Ports zwingen den Programmierer eine Kopie der Einstellung anzulegen. Schwierig verwaltbar, wenn auch andere, gleichzeitig laufende Programm die Einstellung ndern wollen. Deshalb ist auch unter Windows und anderen Multitasking-Systemen der Portzugriff untersagt. 
*	Bedeutungswechsel beim Zugriff auf eine Adresse (um Adreraum zu spraren) Beispiel: Interrupt-Kontroller (Programmierung ber eine Sequenz von Steuerbefehlen auf derselben Adresse) oder Timer (Ablesen und Schreiben des Zhlerstandes in zwei Zugriffen auf derselben Adresse) 
*	Vom Hauptspeicher abweichende Zugriffszeiten
Tastatur, Polling HC04TA1A.C
Die Tastatur stellt einen sehr komplexen Bestandteil des PC dar. Immerhin werden 2 Mikrokontroller gebraucht (einer auf der Tastatur, einer im PC (ab AT)), um Tastatur und PC zu koordinieren. Jede Tastenberhrung veranlat den Mikrokontroller in der Tastatur einen sogenannten SCAN-Kode gemeinsam mit einem Takt an den PC abzusenden. Der empfangende Mikrokontroller 8742 liefert den Kode auf IO-Adresse 0x60 ab und aktiviert die Interruptleitung IRQ1. Der PC und sein BIOS mssen jetzt reagieren. IRQ1 ruft die Interruptserviceroutine ISR9. Diese liest den Scan-Kode von IO-Port 0x60, quittiert den Empfang an Port 0x64 und fllt ein zu dem SCAN-Kode passendes ASCII-Zeichen in die Tastaturwarteschlange. Ist diese voll, piepsts! Genaueres zu diesem Vorgang und auch eine Liste dieser SCAN-Kodes siehe im Beitrag "Dem PC auf die Tasten geschaut", PC-NEWS-21(1/91), S.15..21. 
Unter Polling versteht man die zyklische Abfrage einer Datenquelle, bei der man eine von auen kommende nderung erwartet. Polling ist schnell - wenn man sonst nichts zu tun hat. Ist Polling allerdings in ein komplizierteres Programm eingebunden, sinkt die Abfragehufigkeit, dann ist der Zugriff ber Interrupts bedeutend schneller, obwohl bei Interrupts mit einem Overhead gerechnet werden mu. 
HC04TA1A.C
Bevor wir den Vorgngen rund um die Tastatur in einer spteren Folge im Detail nachgehen, wollen wir durch Polling feststellen, wie diese SCAN-Kodes beschaffen sind. Das Programm fragt eine nderung an Port 0x60 ab und stellt den Code am Bildschirm dar. Wir sind noch weit vom ASCII-Code entfernt, es kommt nur eine laufende Nummer pro Taste. Die Taste mit Nummer 1 bricht das Programm ab! Welche ist das?
Wir brauchen eine Hilfsvariable oldkey, in der der jeweils aktuelle Wert an Port 0x60 gespeichert wird. Solange sich dieser Wert nicht ndert, wird nichts ausgegeben. Die nderung bewirkt die Ausgabe und auerdem wird oldkey=key;  weiter gehts! 
Wieviele Kodes werden pro Tastenbettigung geschickt? Wie unterscheiden sich diese? Gibt es auch Tasten, die mehr als einen Kode senden? Wie verhalten sich die Tasten SHIFT, CTRL, ALT? Sendet jede Taste einen Kode? Haben die Kodes eine Systematik? Wie sieht die Kodefolge beim Liegenlassen der Taste aus? 
Programmierstil HC04TA1B.C..1F.CPP
So, wie das vorige Programm sehen etwa Schlerprogramme aus. Es werden BORLAND-spezifische Funktionen hier cprintf() und clrscr() verwendet, daher ist das Programm weniger portierbar. Wir wollen das Programm durch einige Manahmen etwas hherwertiger gestalten. 
HC04TA1B.C
Dieses und das folgende Beispiel zeigen, wie man die Portierbarkeit auf andere Rechner und Kompiler erhhen kann. Eine Manahme zur Verbesserung der Portierbarkeit ist die Verwendung von ANSI-Funktionen, printf() statt cprintf() und CLS() statt clrscr(). CLS() ist ein Makro, das unter Verwendung von printf() den ANSI-ESC-Kode fr die Bildschirmlschung an stdout absendet, vorausgesetzt, der Treiber ANSI.SYS ist geladen. 
BORLANDC	ANSI-Ccprintf()	printf()conio.h	stdio.hclrscr()	CLS()     /* printf("\033[2J") */
HC04TA1C.C
Ersetzt man die konkreten Zahlenwerte im Programm durch Makros, kann der Leser besser erkennen, was die Zahlenwerte bedeuten. Ebenfalls der Zahlenwert fr den SCAN-Kode, der das Programmende bestimmt. Die an und fr sich nicht portierbare Funktion inportb() existiert nur bei BORLAND und wird daher durch ein Makro ersetzt, das fr jeden neuen Kompiler einfach anpabar ist. Makros erhhen die Portierbarkeit! 
HC04TA1D.CPP
Die so definierten Adressen und Kodes werden am besten in eigenen Header-Dateien (iodef.h, keyscan.h) zusammengefat, damit sie spter bei Bedarf wieder zur Verfgung stehen. Damit bekommen alle Programme zunehmend einheitliches Aussehen. Auch wollen wir die neuen Mglichkeiten von C++ nutzen, daher wird die Dateiendung auf CPP gendert. Wir verwenden zwar noch keine selbstdefinierten Klassen (,das kommt erst in der Version HC04TA1G.CPP dieses Programms) aber wir benutzen aus der Stream-Bibliothek die Objekte cout und cin, sowie die berladenen Operatoren << und >>.
C	C++printf	cout <<scanf	cin >>printf("%02x\n")	cout <<setfill('0')<<setw(2)<<hex<<endl
HC04TA1E.CPP
Unser Ziel war wiederverwertbarer Kode. Darunter versteht man die Aufspaltung eines 'Bandwurm'-programms in Funktionen mit allgemeiner Verwendungsmglichkeit. Damit wird das Programm immer etwas lnger und langsamer aber auch lesbarer (, da anstelle eines 'Bandwurm'-teils eine verstndliche Funktionsbezeichnung tritt) und eben wiederverwendbarer (, da die Funktion gleich etwas allgemeiner abgefat wird, um auch in anderen Fllen zur Stelle zu sein). 
In unserem Beispiel bieten sich dazu zwei Dinge an: Lschen des Tastaturbuffers (clear_kbdqueue()) und Lesen des nchsten Scankodes (read_kbd()). Diese Funktionen werden in diesem Schritt aus dem Hauptprogramm herausgelst und selbstndig dokumentiert ans Programmende gestellt. An den Programmanfang kommen die Prototypen dieser Funktionen. Die Funktion read_kbd() wird dabei gleich etwas bereichert, es gibt einen Parameter, hnlich, wie bei bioskey(), wobei 0 'Warten auf nderung' bedeutet und 1 'Lesen ohne Warten'. 
HC04TA1F.CPP
Wenn man bei jedem Programm diese Trennung in verwendbare Funktionen durchfhrt, mte man beim Wiederverwenden den jeweiligen Funktionstext in das nchste Programm kopieren. Dabei entstnden im Laufe der Zeit soviele Programmversionen als Programmkopien angefertigt wurden, da man immer mit nderungen rechnen mu. Es gibt zwei Mglichkeiten, das zu verhindern: Man ordnet Funktionskode und Hauptprogramm in getrennten Dateien an und (1) inkludiert immer den Sourcekode des Funktionsteils in den Modulen, in denen dieser bentigt wird oder (2) bildet eine eigene Bibliothek aus diesen Funktionen, hier mylib.lib, und beschreibt diese durch eine Headerdatei mylib.h, wie das auch der Kompiler mit der C-Bibliothek macht. Mit jedem neu geschriebenen Programm wchst die eigene Bibliothek, mit jeder Wiederverwendung werden die Module perfekter, da immer mehr zunchst unerkannte Fehler ausgemerzt werden. Die beiden Funktionen kbd_read() und clear_kbdqueue() werden gemeinsam mit vielen anderen Funktionen zur Bibliothek MYLIB.LIB zusammengefat. Damit die Programme auch ohne die Bibliothek ablaufen und bei nderungen der Module nicht immer die gesamte Bibliothek neu gebildet werden mu, wird in diesem Programm der Quellkode kbd.c bedingt eingebunden. Wird in der Projektdatei fr die Bibliothek dann das Makro MYLIB definiert, entfllt die Einbindung des Source-Kode, das Programm mu nicht umgeschrieben werden. Diese Manahme werden Sie bei allen folgenden Programmen finden. 
.D.C:\MYLIB\SAMPLE\HC04TA1A.C,/* HC04TA1A.C *//* * Tastatur-kodes prfen, BORLAND-Version * ====================================== */#include <dos.h>      /* inportb() */#include <bios.h>     /* bioskey() */#include <conio.h>    /* cprintf() */void main(void){  unsigned char key, oldkey;  clrscr();  cprintf("Tastatur-SCAN-Codes, Ende mit ESC\n\r");  oldkey=inportb(0x60);  do  {    key=inportb(0x60);    if (key!=oldkey)    {      cprintf("%2x ",key);      oldkey=key;    }    // Abholen und ignorieren des Tastenkode    while (bioskey(1)) bioskey(0);  }  while (key!=1);  clrscr();}.D.
.D.C:\MYLIB\SAMPLE\HC04TA1B.C,/* HC04TA1B.C *//* * Tastatur-kodes prfen, ANSI-Version * =================================== */#include <dos.h>      /* inportb() */#include <bios.h>     /* bioskey() */#include <stdio.h>    /* printf()  */#include <stdlib.h>   /* exit()    */#include <ansi.h>     /* CLS()     *//* ansi.h: #define CLS() printf("\033[2J") */void main(void){  unsigned char key, oldkey;  CLS();  printf("\n\nTastatur-SCAN-Codes, Ende mit ESC\n");  oldkey=inportb(0x60);  do  {    key=inportb(0x60);    if (key!=oldkey)    {      printf("%2x ",key);      oldkey=key;    }  }  while (key!=1);  CLS();}.D.
.D.C:\MYLIB\SAMPLE\HC04TA1C.C,/* HC04TA1C.C *//* * Tastatur-kodes prfen, mit Makros * ================================= */#include <bios.h>#include <conio.h>#include <mytypes.h>#define PC_KBD_DATA 0x60#define SCAN_ESCAPE 1#define IN_PORT inportbVOID main(VOID){  UCHAR key, oldkey;  clrscr();  cprintf("Tastatur-SCAN-Codes, Ende mit ESC\n\r");  oldkey=IN_PORT(PC_KBD_DATA);  do  {    key=IN_PORT(PC_KBD_DATA);    if (key!=oldkey)    {      cprintf("%2x ",key);      oldkey=key;    }    while (bioskey(1)) bioskey(0);  }  while (key!=SCAN_ESCAPE);  cprintf("\n\r");  clrscr();}.D.
.D.C:\MYLIB\SAMPLE\HC04TA1D.CPP,/* HC04TA1D.CPP *//* * Tastatur-kodes prfen, CPP-Version * ================================== */#include <bios.h>     // bioskey()#include <portable.h> // cout, setw(), setfill()                       // IN_PORT#include <mytypes.h>  // VOID, UCHAR...#include <iodef.h>    // PC_KBD_DATA#include <keyscan.h>  // SCAN_ESCAPEVOID main(VOID){  UCHAR key, oldkey;  cout << "Tastatur-SCAN-Codes, Ende mit ESC" << endl;  oldkey=IN_PORT(PC_KBD_DATA);  do  {    key=IN_PORT(PC_KBD_DATA);    if (key!=oldkey)    {      cout << setfill('0') << setw(2) << hex           << (UINT)key << ' ';      oldkey=key;    }    while (bioskey(1)) bioskey(0);  }  while (key != SCAN_ESCAPE);  cout << endl << endl;}.D.
.D.C:\MYLIB\SAMPLE\HC04TA1E.CPP,/* HC04TA1E.CPP *//* * Tastatur-kodes prfen, wiederverwendbarer Kode * ============================================== */#include <bios.h>    // bioskey()#include <iodef.h>   // PC_KBD_DATA#include <keyscan.h> // SCAN_ESCAPE#include <mytypes.h> // INT, UCHAR...#include <portable.h>// IN_PORT                     // cout, setw(), setfill()INT cdecl kbd_read(INT mode);VOID cdecl clear_kbdqueue(VOID);VOID main(VOID){  UCHAR key;  cout << "Tastatur-SCAN-Codes, Ende mit ESC" << endl;  do  {    key=kbd_read(0);    if (key!=SCAN_ESCAPE)      cout << setfill('0') << setw(2) << hex           << (UINT)key << ' ';    clear_kbdqueue();  }  while (key!=SCAN_ESCAPE);  cout << endl << endl;}/* * UCHAR kbd_read(INT mode) * ======================== * ENTRY: mode=0 ... wait for change on kbd-port *        mode=1 ... immediate return * EXIT:  returns SCAN-code */INT cdecl kbd_read(INT mode){  UCHAR key;  static UCHAR oldkey=0;  switch (mode)  {  case 0:    do      key=IN_PORT(PC_KBD_DATA);    while (key==oldkey);    break;  case 1:    key=IN_PORT(PC_KBD_DATA);    break;  }  oldkey=key;  return key;}/* * VOID clear_kbdqueue(VOID) * ========================= * clears keyboard queue */VOID cdecl clear_kbdqueue(VOID){  while (bioskey(1)) bioskey(0);}.D.
Diese schrittweise Verallgemeinerung des Kodes wurde nur zu Beginn gezeigt. Jetzt wollen wir versuchen, die letzte Version auf einen anderen Port zu bertragen. 
Maus, Polling HC04MO1.CPP
Jeder Port, an dem sich ndernde Daten anliegen, kann hnlich abgefragt werden. Das folgende Programm liest die Daten von der Maus. Es setzt voraus, da die Maus an COM-Port-1 angeschlossen ist. Den exakten Wert fr anderere Ports entnehmen Sie in Analogie zu 0x3f8 aus der Belegungstabelle der IO-Ports in iodef.h. Die Funktion mouse_read() entspricht der Funktion kbd_read(), hat aber einen zustzlichen Parameter bekommen, der die Portnummer bestimmt. 
.D.C:\MYLIB\SAMPLE\HC04MO1.CPP,/* HC04MO1.CPP *//* * Maus-Kodes prfen * ================= */#include <portable.h> // cout#include <keyscan.h>  // SCAN_ESCAPE#include <mylib.h>    // mouse_read(), clear_kbdqueue()#ifndef MYLIB#include "\mylib\source\mou.c"#include "\mylib\source\kbd.c"#endif#define COMPORT 1#define MOUSE_SCAN 1#define MOUSE_WAIT 0VOID main(VOID){  UCHAR mouse, oldmouse=0;  constream con;  con.clrscr();  con << "Maus-Daten, Ende mit Taste" << endl << endl;  do  {    mouse=mouse_read(COMPORT,MOUSE_SCAN);    if (mouse!=oldmouse)    {      con << setw(2) << setfill('0') << hex          << (UINT)mouse << ' ';    }    oldmouse=mouse;  }  while (!kbhit());  clear_kbdqueue();  clrscr();}.D.
Die Funktion mouse_read() ist in der Datei mou.c zu finden, der Prototyp in mylib.h, der bersetzte Funktionskode in mylib.lib. 
Wenn Sie dieses Programm testen, knnen Sie versuchen herauszufinden, welche Kodes die Maus in bestimmen Situationen sendet und welchen Kode die verschiedenen Maustasten haben! 
Timer, Polling HC04TI0.CPP
                  Adresse  Schreiben           LesenĿ Channel 0      0x40     Anfangszhlerstand  ZhlerstandĴ Channel 1      0x41     Anfangszhlerstand  ZhlerstandĴ Channel 2      0x42     Anfangszhlerstand  ZhlerstandĴ Control        0x43     Steuerwort          -
Die Programmierung der Timer sehen wir uns spter genauer an, jetzt wollen wir nur das Auslesen der Timer versuchen. Wir haben 3 Kanle mit je 16bit Zhlbreite zur Verfgung. Die Timer werden mit derselben Frequenz von 1,19MHz gespeist, haben aber verschiedene Anfangszhlerstnde. Der Timer, beginnt beim programmierbaren Anfangszhlerstand, nach abwrts, Richtung 0 zu zhlen und beginnt danach wieder von vorne. 
Das Programm liest zweimal hintereinander dieselbe Adresse, in unserem Programmbeispiel 0x40 (=Kanal 0). Wollten wir die beiden anderen Kanle auslesen, mten wir die Adressen 0x41 (Kanal 1) oder 0x42 (Kanal 2) einsetzen. Der Timer ist so programmiert, da beim ersten Lesevorgang das niederwertige, danach das hherwertige Byte gelesen wird. 
Waren die vorangegangenen Beispiele nur von der Benutzerreaktion abhngig (die Reaktion erfolgte mit unmerklicher Verzgerung), haben wir es hier mit der Beobachtung eines raschen Zhlvorganges durch ein Programm zu tun. Die Periodendauer des Zhlvorgangs ist etwas kleiner als 1 ms. Aus dem Abstand zweier abgelesener Werte knnen Sie sich ein Bild von der Geschwindigkeit von C machen: Der Programmabbruch erfolgt nicht durch die Funktion bioskey(), sondern nach 300 Abfragen. Je nachdem, wie man die Bildschirmausgabe anordnet (innerhalb oder auerhalb der Polling-Schleife) ist der Abfrage-Abstand um Grenordnungen verschieden. 386SX-Notebook, 20MHz: 
Bildausgabe auerhalb: 15 usBildausgabe innerhalb: 2700 us
Stellen Sie fest, welchen Zhlzyklus die drei Timer haben (Anfangswerte)! Welcher Timer zhlt nicht? (Programm HC04IO5.CPP schaltet ihn ein, Objekt tim2gat gengt.) 
.D.C:\MYLIB\SAMPLE\HC04TI0.CPP,/* HC04TI0.CPP *//*  * Lesen des Timers * ================ */#include <portable.h>#include <mylib.h>#ifndef MYLIB#include "\mylib\source\tim.c"#endif#define READINGS 300LONG timer_readings[READINGS];VOID main(VOID){  constream con;  con.clrscr();  for (INT i=0; i<READINGS; i++)  {    timer_readings[i]=tim0_read();//    cout << setw(4) << setfill('0') << hex//         << timer_readings[i] << ' ';  }  ULONG differenz=0;  INT n=0;  for (i=0; i<READINGS; i++)  {    if (i)    {      if (timer_readings[i]<timer_readings[i-1])      {        n++;        differenz+=          (timer_readings[i-1]-timer_readings[i]);      }    }    cout << setw(4) << setfill('0') << hex         << timer_readings[i] << ' ';  }  cout << endl       << n << " Differenzen gemittelt" << endl;  cout << "Zhlerstandsdifferenz : "       << dec << (UINT)(differenz/n) << endl;  cout << "Schleifendurchlaufzeit : "       << (differenz/n)/(1.19) << " us" << endl;  cin.get();}.D.
Aus den Beobachtungen knnen wir mehrere Schlsse ziehen: Das Ablesen der Zhlerstnde ist zwar mglich, ein bestimmter Zhlerstand kann aber nicht abgefragt werden. Am ehesten ist noch eine Abfrage auf kleiner gleich mglich. Es ist daher wesentlich prziser mit dem Timer-Interrupt zu arbeiten, als sich auf eine Abfrage einzulassen; das wollen wir spter unternehmen. Jetzt knnen wir uns merken, da die Interruptroutine sparsam zu programmieren sein wird, will man dem Vordergrundprogramm nicht allzuviel Zeit wegnehmen, dh. z.B. keine Bildausgaben. 
Allgemeine Ein- Ausgabe, IO-Klassen IO.HPP, IO.CPP
Haben Sie es auch bemerkt? Wir haben zwar erfolgreich ein fr die Tastatur, fr die Maus und auch fr den Timer anwendbares Unterprogramm geschrieben, diese Programme unterscheiden sich aber nur in der Adressen, die sie verwenden. Man kann sich daher eine allgemeinere Ansprache der IO-Ports vorstellen, als es bisher geschah. C++ bietet uns dabei Hilfen von bisher unbekannter Qualitt an. 
Whrend Hauptspeicheradressen bei Belegung durch eine Variable vielfltig bearbeitbar sind (+,-,*,++,>>,<<..) mu man beim Handhaben von IO-Ports im allgemeinen eine Kopie des Portinhalts im Haupspeicher bearbeiten und dann wieder an der Port bergeben. Bearbeitungsreihenfolge: 
Lesen	inport() Bearbeiten	+-*++>>...Verndern	outport() 
Beim Lesen und Verndern mu immer auch darauf geachtet werden, die richtige Adresse als Operand zu bergeben. Auerdem gibt es IO-Ports, die nur zum Lesen geeignet sind, andere nur zum Schreiben und wieder andere schreib- und lesbar sind, wobei hier wieder unterschieden werden mu zwischen Ports, die rcklesbar sind (parallele Schnittstelle) und Ports, die in Schreib- und Leserichtung verschiedene Funktion haben (serielle Schnittstelle). Dazu kommen auch komplexere IO-Bedienungsfunktionen, wie sequentiel mehrere Bytes mit verschiedener Funktion nacheinander zu schreiben. Besonders die nicht-rcklesbaren Ports erfordern vom Programmierer die Verwaltung einer Variablen im Hauptspeicher, die den jeweils aktuellen Wert enthlt. Es gibt also beim Umgang mit IO-Ports Vieles zu bercksichtigen. 
Bei der Bemhung um Verallgemeinerung der Portansprache wurde eine C++-Klasse IO mit daraus abgeleiteten Klassen IOBYTE, IOBYTE_IN, IOBYTE_OUT, IOBYTE_IO, IOBYTE_LOOP, IOBIT_IN, IOBIT_OUT, IOBIT_IO, IOBIT_LOOP gebildet. Diese Klassen haben folgenden Zusammenhang: 
IO                     BasisklasseIOBYTE               Simulation ohne PortzugriffIOBYTE_IN            Byte-Input-Port                        IOBYTE_OUT           Byte-Output-Port                        IOBYTE_IO            Byte-Rcklesbarer PortIOBIT_IN             Bit-Input-Port              IOBIT_OUT            Bit-Output-Port              IOBIT_IO             Bit-Rcklesbarer PortIOBIT_LOOP           Testschleife zweier Bits
Alle Klassen sind in der Datei IO.HPP definiert. Die Namen sind grtenteils selbsterklrend. Alle Portklassen werden von einer Basisklasse IO abgeleitet, von derselbst keine Objekte ableitbar sind, da die Konstruktoren dieser Klasse nur von abgeleiteten Klassen erreichbar sind (protected). Man kann einen IO-Port nur zum Lesen (IOBYTE_IN) oder nur zum Schreiben (IOBYTE_OUT) oder zum Schreiben und Lesen (IOBYTE_IO) = rcklesbarer IO-Port generieren. Man kann durch die entsprechenden Bit-Klassen IOBIT_IN, IOBIT_OUT und IOBIT_IO dasselbe fr ein einzelnes Bit eines Ports durchfhren. Die selbstndige Klasse IOBIT_LOOP benutzt zwar zwei Objekte des Bit-Typs ist aber nicht direkt von IO abgeleitet. IOBIT_LOOP beschreibt einen Kurzschlu zwischen einem als Ausgang und einem als Eingang beschriebenen Port-Bit, wie es zu Testzwecken oft erforderlich ist. Die Klasse IOBYTE ist ein Behelf zum Testen der Ausgabefunktion und wird beim Programm HC04CM1A.CPP fr das CMOS-RAM verwendet. 
Die meisten Funktionen sind inline-Funktionen, erfordern daher keinen Funktionsaufruf bei der Ausfhrung. Der Kode ist in IO.CPP enthalten. Besonders eindrucksvoll sind dabei die berladenen Operatoren. Was leisten nun diese Klassen im Detail?
*	Die Klassen unterscheiden zwischen bitweiser und byteweiser Ansprache
*	Ports, die als Inputs definiert werden, knnen nicht beschrieben werden und umgekehrt, knnen Outputports nicht gelesen werden, es fehlen einfach die entsprechenden Zugriffsfunktionen, lediglich die Klasse .._IO umfat alle Mglichkeiten. 
*	Der letzte Wert in einem In- oder Outputport kann jederzeit, auch bei nicht rcklesbaren Ports, geholt werden (Funktion val()), dasselbe gilt fr den Wert davor (Funktion old()). 
*	Fr Outputports knnen einfache Operatorenrechungen angewendet werden, wie +=,-=,*=,/=,%=,++,--,&=,|=,~,^=,<<,>>.
*	Bei Input-Ports kann auf eine nderung gewartet werden
*	Die Gltigkeit des Adrebereiches wird berprft. 
*	Bitein- und ausgnge werden hnlich wie Byte-Ports behandelt. 
*	Bildschirmausgabe der Portinhalte wird durch berladen des Operators << implementiert
*	Ein definierter Port kann nur einmal verwendet werden, versehentliche Doppelbelegungen erzeugen einen run-time-Fehler, d.h. die Bearbeitung von Hardware ber die IO-Klassen bindet eine 'Isolierschicht' zwischen Ports und Anwendung. 
	Damit diese speziellen Schutzmechanismen der IO-Klasse (Verhinderung von Doppelbenutzung von Ports) wirksam werden knnen, mu man alle Ein- und Ausgaben ber die IO-Klasse abwickeln. 
Was fehlt?
*	Abgeleitete Klassen fr komplexere Portansprache
*	Abgeleitete Klassen (oder eine Erweiterung der Basisklasse IO) zu Ansprache mehrerer zusammengehriger Bits innerhalb eines Ports. 
.D.C:\MYLIB\INCLUDE\IO.HPP,#ifndef __IO_HPP#define __IO_HPP#include <mytypes.h>#define P_IO#include <portable.h>#include <mylib.h>#include <bit.h>#define GETPORT() intoff();Old=IN_PORT(Adr)//#define GETPORT() intoff();Old=Val;Val=IN_PORT(Adr)#define SETPORT() OUT_PORT(Adr,Val);inton()#define PC_MAXPORTS 0x400#define TRENN_OUT ':'#define TRENN_IO  '='#define TRENN_BIT '#'#define DISP_IN  "->"#define DISP_OUT "<-"#define DISP_IO  "<>"extern BOOL first_IO;enum DIRECTION{  IN,  OUT,  IN_OUT};enum PORT{  BYTE,  BIT};enum IO_ERR{  IO_NOERROR = 0,  IO_ERR_ADDR,  IO_ERRS_IOTYP,  IO_ERRS_LEN,  IO_ERRS_ADDR,  IO_ERRS_BITN,  IO_ERRS_DAT,  IO_ERRBIT_NR,  IO_ERR1,  IO_ERR0};enum IO_DISP{  IO_DISP_NONE=0,  IO_DISP_BIT=1,  IO_DISP_BYTE=2,  IO_DISP_BOTH=3};struct PORTUSAGE{  UCHAR in;  UCHAR out;};class IO{public:  UCHAR val()    { return (Port==BYTE)?Val:BITTST(Val,Nr); }  UCHAR old()    { return (Port==BYTE)?Old:BITTST(Old,Nr); }  UCHAR byte() { return Val; }  UCHAR oldbyte() { return Old; }  UCHAR bit() { return BITTST(Val,Nr); }  INT adr() { return Adr; }  DIRECTION io() { return Io; }  CHAR nr() { return Nr; }  PORT port() { return Port; }  UCHAR Disp;protected:  IO(INT a, DIRECTION dir, BOOL check);  IO(UCHAR n, INT a, DIRECTION dir, BOOL check);  IO(PORT p, IO_DISP d)    { Port=p; Disp = d; } // nur fr IOBYTE  ~IO();  INT Adr;  DIRECTION Io;  UCHAR Val;  UCHAR Old;  UCHAR Nr;private:  PORT Port;  static PORTUSAGE portusage[PC_MAXPORTS];  BOOL Check;};ostream& operator << (ostream& o, IO& iob);IO_ERR chk (CHAR *s,  INT& adr, DIRECTION& io, UCHAR& val, BOOL disp);IO_ERR chkb(CHAR *s, CHAR& nr,  INT& adr, DIRECTION& io, UCHAR& val, BOOL disp);class IOBYTE : public IO{public:  IOBYTE() : IO(BYTE, IO_DISP_BYTE) { Io=IN_OUT; }  VOID setaddr(INT a) { Adr=a; }  VOID setdata(UCHAR d) { Val=d; }};class IOBYTE_IN : public IO{public:  IOBYTE_IN(INT a,BOOL c=TRUE) : IO(a,IN,c)  { Old=Val=IN_PORT(Adr); }  UCHAR operator () ()    { Old=Val; Val=IN_PORT(Adr); return Val; }  UCHAR get_c();};class IOBYTE_OUT : public IO{public:  IOBYTE_OUT(INT a, UCHAR v,BOOL c=TRUE)  : IO(a,OUT,c)    { OUT_PORT(Adr,v); Old=Val=v; }  IOBYTE_OUT(INT a,BOOL c=TRUE) : IO(a,OUT,c) { }  VOID operator = (UCHAR v)    { Old=Val; Val=v; OUT_PORT(Adr,v); }  UCHAR operator ~ ()    { *this = ~Val; return Val; }  UCHAR operator ++ ()    { *this = (Val+1); return Val; }  UCHAR operator -- ()    { *this = (Val-1); return Val; }  VOID operator += (UCHAR v) { *this = Val+v; }  VOID operator -= (UCHAR v) { *this = Val-v; }  VOID operator *= (UCHAR v) { *this = Val*v; }  VOID operator /= (UCHAR v) { *this = Val/v; }  VOID operator %= (UCHAR v) { *this = Val%v; }  VOID operator <<= (UCHAR v) { *this = Val<<v; }  VOID operator >>= (UCHAR v) { *this = Val>>v; }  VOID operator ^= (UCHAR v) { *this = Val^v; }  VOID operator &= (UCHAR v) { *this = Val&v; }  VOID operator |= (UCHAR v) { *this = Val|v; }};class IOBYTE_IO : public IO{private:  BOOL hide;  VOID inton() { if (hide) enable(); }  VOID intoff() { if (hide) disable(); }public:  IOBYTE_IO(INT a, UCHAR v, BOOL h=TRUE,BOOL c=TRUE)  : IO(a,IN_OUT,c)  { Old=Val=v; hide=h; }  IOBYTE_IO(INT a, BOOL h=TRUE, BOOL c=TRUE)  : IO(a,IN_OUT,c)  { hide=h; }  UCHAR operator () ()    { Old=Val; Val=IN_PORT(Adr); return Val; }  VOID operator = (UCHAR v)    { GETPORT(); Val=v; SETPORT(); }  UCHAR operator ++ ()    { GETPORT(); Val=Old+1; SETPORT(); return Val; }  UCHAR operator -- ()    { GETPORT(); Val=Old-1; SETPORT(); return Val; }  UCHAR operator ~ ()    { GETPORT(); Val=~Old; SETPORT(); return Val; }  VOID operator += (UCHAR v)    { GETPORT(); Val=Old+v; SETPORT(); }  VOID operator -= (UCHAR v)    { GETPORT(); Val=Old-v; SETPORT(); }  VOID operator *= (UCHAR v)    { GETPORT(); Val=Old*v; SETPORT(); }  VOID operator /= (UCHAR v)    { GETPORT(); Val=Old/v; SETPORT(); }  VOID operator %= (UCHAR v)    { GETPORT(); Val=Old%v; SETPORT(); }  VOID operator <<= (UCHAR v)    { GETPORT(); Val=Old<<v; SETPORT(); }  VOID operator >>= (UCHAR v)    { GETPORT(); Val=Old>>v; SETPORT(); }  VOID operator ^= (UCHAR v)    { GETPORT(); Val=Old^v; SETPORT(); }  VOID operator &= (UCHAR v)    { GETPORT(); Val=Old&v; SETPORT(); }  VOID operator |= (UCHAR v)    { GETPORT(); Val=Old|v; SETPORT(); }  IO_ERR tst(ostream& o, BOOL t);};class IOBIT_IN : public IO{public:  IOBIT_IN(CHAR n, INT a, BOOL c=TRUE) : IO(n,a,IN,c)    { Old=Val=IN_PORT(Adr); }  UCHAR operator () ()    { Old=Val; Val=IN_PORT(Adr);      return BITTST(Val,Nr);    }  UCHAR get_c();};class IOBIT_OUT : public IO{public:  IOBIT_OUT(CHAR n,INT a,UCHAR v,BOOL c=TRUE)  : IO(n,a,OUT,c)    { Val=Old=BITMAK(v,n); OUT_PORT(Adr,Val); }  IOBIT_OUT(CHAR n,INT a,BOOL c=TRUE)  : IO(n,a,OUT,c)    { }  VOID operator = (UCHAR v)    { Old=Val; Val=BITINS(Val,Nr,v);      OUT_PORT(Adr,Val);    }  VOID set(UCHAR v)    { Old=Val; Val=v; OUT_PORT(Adr,v); }  UCHAR operator ~ ()    {  Old=Val; Val = BITINV(Val,Nr);       OUT_PORT(Adr,Val); return BITTST(Val,Nr); }  UCHAR operator ++ ()    { return (*this).operator ~ (); }  UCHAR operator -- ()    { return (*this).operator ~ (); }  VOID operator ^= (UCHAR v)    { Old=Val; Val = BITXOR(Val,Nr,v);      OUT_PORT(Adr,Val);    }  VOID operator &= (UCHAR v)    { Old=Val; Val = BITAND(Val,Nr,v);      OUT_PORT(Adr,Val);    }  VOID operator |= (UCHAR v)    { Old=Val; Val = BITOR(Val,Nr,v);      OUT_PORT(Adr,Val);    }};class IOBIT_IO : public IO{private:  BOOL hide;  VOID inton() { if (hide) enable(); }  VOID intoff() { if (hide) disable(); }public:  IOBIT_IO(CHAR n, INT a, UCHAR v,    BOOL h=TRUE,BOOL c=TRUE)  : IO(n,a,IN_OUT,c)    { hide=h; Val=IN_PORT(Adr); Val=BITMAK(Val,n);      Old=Val; OUT_PORT(Adr,Val); }  IOBIT_IO(CHAR n, INT a,    BOOL h=TRUE,BOOL c=TRUE)  : IO(n,a,IN_OUT,c)    { hide=h; Old=Val=IN_PORT(Adr); }  VOID operator = (UCHAR v)  { GETPORT(); Val = BITINS(Old,Nr,v); SETPORT(); }  UCHAR operator () ()    { Old=Val; Val=IN_PORT(Adr);      return BITTST(Val,Nr);    }  UCHAR operator ~ ()  { GETPORT(); Val = BITINV(Old,Nr); SETPORT();    return BITTST(Val,Nr);  }  UCHAR operator ++ ()    { return (*this).operator ~ (); }  UCHAR operator -- ()    { return (*this).operator ~ (); }  VOID operator ^= (UCHAR v)    { GETPORT(); Val=BITXOR(Old,Nr,v); SETPORT(); }  VOID operator &= (UCHAR v)    { GETPORT(); Val = BITAND(Old,Nr,v); SETPORT(); }  VOID operator |= (UCHAR v)    { GETPORT(); Val = BITOR(Old,Nr,v); SETPORT(); }  IO_ERR tst(ostream& o, BOOL t);  INT looperror();};class IOBIT_LOOP{public:  IOBIT_LOOP(INT txa, CHAR txb, INT rxa, CHAR rxb,             BOOL pol)    { tx = new IOBIT_OUT(txb,txa);      rx = new IOBIT_IN(rxb,rxa);      polarity=pol; make = TRUE; }  IOBIT_LOOP(IOBIT_OUT *out, IOBIT_IN *in, BOOL pol)    { tx = out; rx = in; polarity=pol; make = FALSE; }  IOBIT_LOOP(IOBIT_OUT& out, IOBIT_IN& in, BOOL pol)    { tx = &out; rx = &in; polarity=pol; make = FALSE; }  ~IOBIT_LOOP()    { if (!make) return; delete tx; delete rx; }  VOID operator = (UCHAR v) { tx->operator = (v); }  UCHAR operator()() { return rx->operator()(); }  INT tst(ostream& o);private:  IOBIT_OUT *tx;  IOBIT_IN *rx;  BOOL polarity;  BOOL make;};#endif // __IO_HPP.D.
Ein IO-Objekt wird durch die geschtzten Variablen Val (der zuletzt gelesene oder der zuletzt geschriebene Wert), Old (der davor gelesene Wert), Adr (Adresse), Io (Eingangs-, Ausgangs- oder bidirektionaler Port), Nr (Bitnummer, nur bei Bit-Ports), Port (Bit- oder Byte-Port) und Check beschrieben. Die Variablen haben jeweils groe Anfangsbuchstaben, whrend die sie zurcklesenden, ffentlichen Funktionen val(), adr(), io(), nr() und port() kleine Anfangsbuchstaben haben. Die Variablen sind gegen Zugriff von Programmen auerhalb der Klasse geschtzt, abgeleitete Klassen knnen aber mit ihnen arbeiten. Check ist fr die berprfung von Doppelbenutzung von Ports vorgesehen, der Destruktor bentigt diese Variable, um die Belegung wieder frei geben zu knnen. 
Ein IO-Objekt merkt sich in der Variablen Val den zuletzt gelesenen oder den zuletzt geschriebenen Wert. Die Funktion val() liefert den zuletzt gelesenen Wert. Die Funktion bit() leistet dasselbe fr ein Bit-Objekt. 
Allgemeine Vereinbarungen
IN und OUT sind Makros, mit denen bei den rcklesbaren Ports der Port zuerst gelesen und dann gesetzt wird. Dieser Vorgang kann wahlweise mit ein- oder ausgeschalteten Interrupts erfolgen. Je nachdem, wie der alte Wert old gebildet wird, kann auch eine andere Lesereihenfolge angewendet werden (durch // ausgeschaltet). Das Schalten der Interrupts erfolgt nicht unmittelbar ber enable(), disable(), sondern ber inline-Funktionen inton() und intoff() und eine bei der Konstruktion bergebene Variable hide (Anfangswert: TRUE) bestimmt, ob die Interrupts geschaltet werden oder nicht. 
PC_MAXPORTS legt die grte erlaubte IO-Adresse fest. Alle greren Adressen werden auf diesen Maximalwert abgeschnitten. 
Die Zeichen TRENN_OUT, TRENN_IO und TRENN_BIT  dienen zur Trennung der Eingabedaten, wenn ein Port oder ein Portbit als Zeichenfolge eingegeben wird (siehe Beispiel HC04IO2.CPP). Die Strings DISP_IN, DISP_OUT  und DISP_IO trennen Adre- und Datenbestandteil bei der Ausgabe und geben gleichzeitig die bertragungsrichtung an. 
Die Variable first_IO ist beim Programmstart TRUE und bewirkt das Initialisieren der statischen Variablen portusage[]. 
Jeder Bit- oder Byteport kann als Eingang, Ausgang oder bidirektionaler Port betrieben werden. Der Aufzhlungstyp DIRECTION dient zur Beschreibung dieser Porteigenschaft. 
Ein IO-Objekt kann ein BIT- oder BYTE-Objekt sein. Die Basisklasse ist in beiden Fllen die Klasse IO. Die Unterscheidung der Portart bei der Konstruktion eines Objekts bernimmt der Aufzhlungstyp PORT.
Bei der Konstruktion eines IO-Objekts mit einem String werden viele Fehler durch den Fehlertyp IO_ERR beschrieben. 
IO_DISP legt fest, ob eine Anzeige erfolgen soll, und ob die Anzeige des Portwertes oder auch zustzlich die Anzeige des Bits erfolgen soll. 
Der Typ PORTUSAGE legt fest, ob ein Port (oder ein Bit) bereits benutzt ist (in oder out sind 1) und ob es sich um einen Ein-(in=1,out=1) oder Ausgang (in=0,out=1) oder bidirektionalen Port (beide 1) handelt. Wird im Rahmen eines Programms ein Port-Objekt mit derselben Adresse an mehreren Stellen konstruiert, wird das Programm abgebrochen. Diese berprfung ist whrend der Konstruktion eines IO-Objekts abschaltbar. Der Anfangswert ist mit berprfung. 
Basisklasse IO
Die Klasse IO beschreibt entweder einen Bit- oder einen Byte-Port und unterscheidet diese via Konstruktor und durch die private Variable Port. Die geschtzten aber durch abgeleitete Klassen erreichbaren Variablen Adr, Io, Val Old, Nr werden durch die gleichnamigen Zugriffsfunktionen, die aber mit Kleinbuchstaben beginnen, gelesen: adr(), io(), val(), old(), nr(). 
Die Klasse IO dient als Basisklasse fr alle IO-Klassen. Es gibt drei, allerdings geschtzte Konstruktoren, daher kann von dieser Klasse kein Objekt gebildet werden. 
  IO(INT a, DIRECTION dir, BOOL check=TRUE);
Konstruiert einen Byte-Port mit Adresse a, Richtung dir und ausschaltbarer Adreberprfung check. 
  IO(UCHAR n, INT a, DIRECTION dir, BOOL check=TRUE);
Konstruiert einen Bit-Port mit Adresse a, Bitnummer n, Richtung dir und ausschaltbarer Adreberprfung check. 
  IO(PORT p, IO_DISP d) { Port=p; Disp = d; }
Konstruiert eine IO-Objekt ohne Adreangabe. (Wird nur von der Klasse IOBYTE bentigt). 
Der Destruktor ~IO sorgt dafr, da die Adreberprfung fr diesen Port ausgeschaltet wird. 
  UCHAR Disp;
Die ffentliche Variable Disp regelt die Ausgabe ber den berladenen Operator <<. Da ihr Wert nicht besonders kritisch ist, bleibt sie ffentlich, Zugriffsfunktionen entfallen, sie kann die Werte des Aufzhlungstyps IO_DISP (weiter oben) annehmen. 
  static PORTUSAGE portusage[PC_MAXPORTS];
portusage[] ist als statische Variable der Klasse IO auch allen Objekten des IO-Typs gemeinsam. Jedes generierte Objekt setzt ein seiner Adresse entsprechendes Element und auch die Schreib/Leserichtung. Bei einer versehentlichen Doppelbenutzung einer IO-Adresse oder eines bestimmten Bits in verschiedenen Programmteilen wird das Programm mit einer entsprechenden Fehlermeldung abgebrochen. Diese Eigenschaft ist ber den Konstruktor abschaltbar. 
ostream& operator << (ostream& o, IO& iob);
Der Operator << ist fr Objekte der Klasse IO berladen und gibt den Portwert im Format: n:adr##ww aus, wobei n die Bitnummer (entfllt bei Byte-Ports), adr die Adresse, ### ein Symbol, das die Richtung des Ports angibt (<-,<>,->) und ww der Zahlenwert ist. Alle Angaben erfolgen hexadezimal. 
IO_ERR chk(CHAR *s,	   INT& adr, DIRECTION& io, UCHAR& val, BOOL disp);IO_ERR chkb(CHAR *s, CHAR& nr,	   INT& adr, DIRECTION& io, UCHAR& val, BOOL disp);
Ein IO-Objekt kann auch durch einen String konstruiert werden, etwa durch eine Benutzereingabe (siehe Beispiel HC04IO2.CPP). Die Funktionen chk() und chkb() berprfen das Eingabeformat und geben eventuelle Formatfehler bekannt. 
Die Klasse IOBYTE ist eine behelfsmige Klasse, die es erlaubt, ein IO-Objekt zu bilden, dessen Adresse und Daten vernderbar sind. Diese Klasse wird nur bentigt, um die Ausgabefunktion mit dem berladenen Operator << testen zu knnen ohne wirklich einen IO-Port ansprechen zu mssen. Auerdem wird diese Klasse als Hilfsklasse bei der Klasse CMOSRAM benutzt. 
Die Klasse IOBYTE_IN enthlt neben dem Konstruktor fr eine Adresse a die Funktionen operator()() und get_c() mit der aus dem Port gelesen wird. Der Funktionsoperator liest aus dem Port und die Funktion get_c() wartet auf eine nderung des Portwertes. Sie werden wie folgt angewendet:
IOBYTE_IN user(0x300);...UCHAR last = user();
oder
if (user()==5) ...
Die Klasse IOBYTE_OUT enthlt neben zwei Konstruktoren (mit und ohne Initialisierung des Ports) fr eine Adresse a die Operatorfunktionen fr =,++,--,~,<<,>>,+=,-=,*=,/=,%=,&=,|= und ^=, die es ermglichen, mit dem IO-Objekt genauso, wie mit einer Variablen des Typs UCHAR zu arbeiten. Sie werden wie folgt angewendet:
IOBYTE_OUT out(0x300,0);
out=1;++out; --out; ~out; ...
oder
out+=5; out&=0xf0;
Der Operator ~ wirkt auf den Port, obwohl er in dieser Form auf eine Variable nicht anwendbar wre (man mte schreiben out=~out). Die Klasse IOBYTE_IO vereinigt die Mglichkeiten von IOBYTE_IN und IOBYTE_OUT fr einen rcklesbaren Port, wie es etwa der Datenport der parallelen Schnittstelle darstellt und ergnzt diese Mglichkeiten durch die Testfunktion tst(), die die korrekte Funktion des Ports durch alternierendes Anlegen verschiedener Bitmuster testet. Die Portansprache wurde hier noch etwas erweitert: Wenn mehrere Programmteile einen Port bedienen, kann zwischen dem zuletzt gelesenen Wert und dem aktuellen Wert ein Unterschied bestehen, da inzwischen ein anderes interruptgesteuertes Programm diesen verndert haben knnte. Um diesen Effekt zu verhindern, wurde in die Klassen IOBYTE_IO und IOBIT_IO die Mglichkeit eingebaut, die Interrupts vor Zugriff aus- und nachher wiedereinzuschalten. 
Die Klassen IOBIT_IN, IOBIT_OUT und IOBIT_IO entsprechen den BYTE-Klassen arbeiten aber jeweils nur mit einem Bit eines Ports und lassen alle anderen Bits unverndert. Die Anzahl der berladenen Operatoren ist entsprechend geringer, ++, -- und ~ sind identisch. Eine zustzliche Schwierigkeit bei den Bit-Klassen ist, da man zur Vernderung eines Bits jedenfalls den ganzen Port lesen und schreiben mu, die nicht beeinfluten Bits aber unverndert lt. 
Die Klasse IOBIT_LOOP arbeitet mit zwei IOBIT-Objekten tx und rx, die einer Drahtverbindung zwischen zwei beliebigen Portbits entsprechen. Diese Klasse wird beim Testen der seriellen und parallelen Schnittstelle verwendet. 
'Bitprozessor' BIT.H, HC04BI1.CPP
Bei vielen IO-Ports hat jedes einzelne Bit eine andere Bedeutung. Es besteht dabei der Bedarf diese Bits unabhngig voneinander bearbeiten zu knnen. Im allgemeinen werden dafr arithmetische oder logische Operationen verwendet. Einfache Makros in BIT.H vereinheitlichen die Bearbeitung einzelner Bits und reduzieren die Fehleranflligkeit individueller Erfindungen. Um ein Bit in einem Byte zu Setzen, zu Lschen oder zu Invertieren wendet man eine ODER, UND oder EXKLUSIV-Oder Verknpfung mit einer geeigneten Maske an, die das eine Bit beeinflut, die anderen aber unverndert lt. Die Maske kann man als Konstante, als dimensionierte Variable oder als das Ergebnis einer Schiebeoperation gewinnen. Es wurde die Schiebeoperation gewhlt, da in diesem Fall die Makros gleichermaen auf 8- oder 16- oder 32-bit-breite Variablen anwendbar sind. 
.D.C:\MYLIB\INCLUDE\BIT.H,#ifndef __BIT_H#define __BIT_H#define SETMSK(n) (1<<(n))#define CLRMSK(n) (~(1<<(n)))#define BITSET(v,n) ((v)|SETMSK(n))#define BITCLR(v,n) ((v)&CLRMSK(n))#define BITINV(v,n) ((v)^SETMSK(n))#define BITTST(v,n) (((v)&SETMSK(n))!=0)#define BITMAK(b,n) (((b)==0)?0:SETMSK(n))#define BITINS(v,n,b)  (((b)==0)?BITCLR((v),(n)):BITSET((v),(n)))#define BITOR(v,n,b) (((b)==0)?(v):BITSET((v),(n)))#define BITAND(v,n,b) (((b)==1)?(v):BITCLR((v),(n)))#define BITXOR(v,n,b) (((b)==0)?(v):BITINV((v),(n)))#endif.D.
Die MAKROS bestehen aus zwei Gruppen: zunchst jene, die in einer Variablen v das Bit n beeinflussen BITSET, BITCLR und BITINV sowie BITTST, das prft, ob ein Bit gesetzt ist; dann die zweite Gruppe, die mit einer Variablen b arbeiten: BITMAK, generiert in Abhngigkeit von b an der Stelle n ein Bit, BITSET, setzt in v an der Stelle n ein Bit. Die Makros BITOR, BITAND und BITXOR wenden die entsprechenden boolschen Operation auf ein Bit an. Die Anwendung dieser Makros zeigt am besten das Testprogramm:
.D.C:\MYLIB\SAMPLE\HC04BI1.CPP,/* HC04BI1.CPP *//* * TESTEN des 'BITPROZESSORS' * ========================== */#include <portable.h>#include <mytypes.h>#include <bit.h>#define F hex << setw(4)VOID main(VOID){  cout << "Testing unary operations" << endl;  for (INT i=0; i<8; i++)  {    UCHAR v;    cout << "BIT" << i << endl;    for (INT val=0; val<2; val++)    {      if (val==0) v=0; else v=0xff;      cout <<F<< (INT)v << ":SET:" << i << ":"           <<F<< (INT)BITSET(v,i);      cout <<F<< (INT)v << ":CLR:" << i << ":"           <<F<< (INT)BITCLR(v,i);      cout <<F<< (INT)v << ":INV:" << i << ":"           <<F<< (INT)BITINV(v,i);      cout <<F<< (INT)v << ":MAK:" << i << ":"           <<F<< (INT)BITMAK(v,i);      cout <<F<< (INT)v << ":TST:" << i << ":"           <<F<< (INT)BITTST(v,i);      cout << endl;    }  }  cin.get();  cout << "Testing binary operations" << endl;  for (i=0; i<8; i++)  {    UCHAR v;    cout << "BIT" << i << endl;    for (INT val=0; val<2; val++)    {      if (val==0) v=0; else v=0xff;      for (INT j=0; j<2; j++)      {        cout <<F<< (INT)v << ":&:" << j << ":"             <<F<< (INT)BITAND(v,i,j);        cout <<F<< (INT)v << ":|:" << j << ":"             <<F<< (INT)BITOR(v,i,j);        cout <<F<< (INT)v << ":^:" << j << ":"             <<F<< (INT)BITXOR(v,i,j);      }      cout << endl;    }  }  cin.get();}.D.

Demonstration der BYTE-Klassen HC04IO0.CPP, HC04TA1G.CPP
Das folgende Programm demonstriert die Verwendung der rcklesbaren IOBYTE-Klasse am Beispiel der parallelen Schnittstelle. Zum Vergleich wurde als Kommentar die Portansprache in Standard-C angeschrieben. 
.D.C:\MYLIB\SAMPLE\HC04IO0.CPP,/* HC04IO0.CPP *//* * Beispiel fr die Grundfunktionen der IO-Klasse * ============================================== */#include <io.hpp>#include <iodef.h>#ifndef MYLIB#include "\mylib\source\io.cpp"#endifVOID main(VOID){  // IO-Port zum Lesen und Schreiben definieren  IOBYTE_IO lpt(PC_LPT1);  lpt = 0xf0;           // outport(0x3bc,0xf0);  lpt();                // UCHAR a=inport(0x3f8);  cout << lpt << endl;  // cout << (int)a << endl;  lpt ^= 0x55;          // a=inport(0x3bc); a^=0x55;  lpt();                // outport(0x3bc,a);  cout << lpt << endl;  // cout << (int)a << endl;}.D.
Beim Lesen eines Ports kann entweder eine Zuweisung erfolgen: a=lpt() oder, wie im Beispiel, der Funktionsaufruf allein, dann bleibt der Wert in der privaten Variablen Val erhalten und kann entweder durch lpt.val() oder, wie im Beispiel durch die Ausgabe mit dem berladenen Operator << angewendet werden. 
Kehren wir jetzt zu unserem Tastaturprogramm zurck:
.D.C:\MYLIB\SAMPLE\HC04TA1G.CPP,/* HC04TA1G.C *//* * Tastatur-kodes prfen, mit Klasse IO * ==================================== */#include <portable.h> // cout#include <keyscan.h>  // SCAN_ESCAPE#include <mylib.h>    // clear_kbdqueue()#include <iodef.h>    // PC_KBD_DATA#include <io.hpp>#ifndef MYLIB#include "\mylib\source\io.cpp" // IOBYTE_IN#include "\mylib\source\kbd.c"  // clear_kbdqueue()#endifVOID main(VOID){  IOBYTE_IN key(PC_KBD_DATA);  constream con;  con.clrscr();  cout << "Tastatur-SCAN-Codes, Ende mit ESC" << endl;  do  {    if (key.get_c()!=SCAN_ESCAPE)      con << setfill('0') << setw(2) << hex          << (UINT)key.val() << ' ';    clear_kbdqueue();  }  while (key.val()!=SCAN_ESCAPE);  con.clrscr();}.D.
Neue Elemente:
IOBYTE_IN key(PC_KBD)	Objekt key ist ein Leseportkey()	Lesen des SCAN-Kodekey.get_c()	-"-, nderung abwartenkey.val()	liefert zuletzt gelesenen Wert	ohne den Port erneut anzusprechen
Obwohl rein uerlich kein wesentlicher Unterschied zu der vorigen Version HC04TA1F.CPP besteht, beachten Sie, da die Klassen IOBYTE_IN (oder _OUT oder _IO) auf jeden beliebigen Port anwendbar sind; auch der Versuch, durch Parameterbergabe eine gleichwertige Funktionalitt zu erzielen wre nicht genug, die Leistungen der Klasse in C auszudrcken!
Testen der BYTE-Klassen HC04IO1.CPP
Jetzt zu einem Testprogramm, das versucht mglichst alle Operatoren der IOBYTE-Klassen zu benutzen. Vergleichen Sie die Leistung einzelner Aktionen mit der Krze der Darstellung, etwa gibt
cout << kbd
das Format xxx->hh aus oder ++par1 liest den aktuellen Portwert ein, erhht um eins und gibt ihn wieder aus. (Diese Operation knnte auf kbd nicht angewendet werden, da par1 ein IO-Port und kbd nur ein IN-Port ist.) 
Der Test besteht aus zwei Teilen: zuerst wird die parallele Schnittstelle aus unabhngigen Schreib- und Leseports po und pi zusammengesetzt, dann wird ein rcklesbares Objekt pio gebildet. Die Funktion out() vereinheitlicht die Ausgabe, die Makros OUT und OUTIO bergeben jeweils den richtigen Zahlenwert. 
Bei diesem Testprogramm geht es darum, mglichst viele Eigenschaften der Elementfunktionen zu testen. Dazu wird ein rcklesbarer Port (parallele Schnittstelle) verwendet. Der Test besteht aus zwei Teilen, der erste zum Testen von IOBYTE_IN und IOBYTE_OUT, der zweite zum Testen von IOBYTE_IO. Da beide Male derselbe Port (0x3bc) verwendet wird, gibt es innerhalb der Klassengemeinschaft IO*.* wegen der gemeinsamen Variablen portusage[] eine Doppelbelegung und ohne weitere Manahme wrde das Programm in der Mitte mit einer Fehlermeldung abbrechen. Es gibt zwei Mglichkeiten das zu verhindern: (a) die beiden Programmteile werden als getrennte Blcke behandelt, wie im Beispiel, denn dann wird automatisch am Ende der ersten Blockes der Destruktor von pi und po gerufen, der die Belegung in portusage[] zurcknimmt, oder (b) der Konstruktor von pio wird mit zustzlichen Parametern gerufen pio(PC_LPT1|LPT_DATA,TRUE,FALSE), wobei TRUE die Interruptabschaltung aktiviert und FALSE die berprfung der Doppelbelegungen ausschaltet. Hier wurde (a) angewendet, da es gar nicht notwendig ist, da alle drei Objekte pi, po und pio gleichzeitig aktiv sind, pi und po haben ihre Schuldigkeit getan und werden nicht mehr bentigt. 
.D.C:\MYLIB\SAMPLE\HC04IO1.CPP,/* HC04IO1.CPP *//*  * Testen der Klassen IOBYTE_IN, IOBYTE_OUT, IOBYTE_IO * =================================================== */#include <portable.h>#include <iodef.h>#include <keyscan.h>#include <mylib.h>#include <io.hpp>#ifndef MYLIB#include "\mylib\source\io.cpp"#endif#define F hex<<setw(2)<<setfill('0')#define OUT(o,a,n) out(o,a.old(),n())#define OUTIO(o,io) out(o,io.old(),io.val())VOID out(CHAR* operation, UCHAR alt, UCHAR neu){  cout << operation <<F<< ' ' ;  cout << "alt:" <<F<< (INT)alt << ' '       << "neu:" <<F<< (INT)neu << ' ' << endl;}INT main(VOID){  cout << endl       << "IOBYTE_OUT/IOBYTE_IN: Operatoren-Test,\n";  {    IOBYTE_IN pi(PC_LPT1|LPT_DATA);    IOBYTE_OUT po(PC_LPT1|LPT_DATA);    po=0x13;     OUT("po=0x13:   ",po,pi);    ++po;        OUT("++po:      ",po,pi);    --po;        OUT("--po:      ",po,pi);    po += 5;     OUT("po+=5:     ",po,pi);    po -= 5;     OUT("po-=5:     ",po,pi);    po *= 5;     OUT("po*=5:     ",po,pi);    po /= 5;     OUT("po/=5:     ",po,pi);    po = 13;     OUT("po=13:     ",po,pi);    po %= 5;     OUT("po%=5:     ",po,pi);    ~po;         OUT("~po:       ",po,pi);    po &= 0xf0;  OUT("po$=0xf0:  ",po,pi);    po |= 0xf0;  OUT("po|=0xf0:  ",po,pi);    po ^= 0xff;  OUT("po^=0xff:  ",po,pi);    po <<= 2;    OUT("po<<=2:    ",po,pi);    po >>= 2;    OUT("po>>=2:    ",po,pi);  }  cout << endl       << "IOBYTE_IO: Operatoren-Test, \n";  cin.get();  {    IOBYTE_IO pio(PC_LPT1|LPT_DATA);    pio=0x13;    OUTIO("pio=0x13: ",pio);    ++pio;       OUTIO("++pio:    ",pio);    --pio;       OUTIO("--pio:    ",pio);    pio += 5;    OUTIO("pio+=5:   ",pio);    pio -= 5;    OUTIO("pio-=5:   ",pio);    pio *= 5;    OUTIO("pio*=5:   ",pio);    pio /= 5;    OUTIO("pio/=5:   ",pio);    pio = 13;    OUTIO("pio=13:   ",pio);    pio %= 5;    OUTIO("pio%=5:   ",pio);    ~pio;        OUTIO("~pio:     ",pio);    pio &= 0xf0; OUTIO("pio$=0xf0:",pio);    pio |= 0xf0; OUTIO("pio|=0xf0:",pio);    pio ^= 0xff; OUTIO("pio^=0xff:",pio);    pio <<= 2;   OUTIO("pio<<=2:  ",pio);    pio >>= 2;   OUTIO("pio>>=2:  ",pio);  }  cout << "Erfolgreich beendet" << endl;  return EXIT_SUCCESS;}.D.
Demonstration der BIT-Klassen HC04IO5.CPP
Wie spter noch ausfhrlich gezeigt wird, mssen fr die Tonausgabe am PC-Lautsprecher 2 Bits gesetzt werden. Die beiden Bits sind Ausgnge am sogenannten PPI, Port B. Im folgenden Programm werden die beiden Bits durch zwei Bit-Objekte tim2gate und tim2data reprsentiert. tim2gate verbindet den Takt mit 1.19 MHz mit dem Timereingang, tim2data verbindet den Lautsprecher mit Timer. Der Lautsprecher wird durch die Tasten '1' ein- und durch '0' ausgeschaltet. ESC beendet das Programm. Beachten Sie die 2 verschiedenen Konstruktionsmglichkeiten fr Bit-Objekte:
(a) IOBIT_IO objekt(n,a), wobei n die Bitnummer ist und a die Adresse im PC-Adreraum (diese Mglichkeit ist im Beispiel durch // ausgeschaltet) und
(b) IOBIT_IO objekt(bitadresse,ba), wobei ba die erste Adresse eines aus mehreren Registern bestehenden Bausteins ist und bitadresse aus den nibbles Bitnummer und relative Registeradresse besteht. Diese Mglichkeit (b) ist immer dann vorteilhaft, wenn der IO-Baustein, wie bei der parallelen oder seriellen Schnittstelle mehrfach im Adreraum gemappt ist. In der Adreliste in iodef.h ist dann nur die Basisadresse angegeben und am Ende von iodef.h die Bitadreliste der einzelnen Bausteine. 
.D.C:\MYLIB\SAMPLE\HC04IO5.CPP,/* HC04IO4.CPP *//* * Bearbeitung einzelner Bits * ========================== */#include <portable.h>#include <iodef.h>#include <bios.h>#include <io.hpp>#ifndef MYLIB#include "\mylib\source\io.cpp"#endifINT main(VOID){  cout << endl       << "Lautsprecher schalten, Ende mit ESC"       << endl;//  IOBIT_IO tim2gate(0,XT_PPI_B);//  IOBIT_IO tim2data(1,XT_PPI_B);  IOBIT_IO tim2gate(B_PPI_TIM2GATE,XT_PPI);  IOBIT_IO tim2data(B_PPI_TIM2DATA,XT_PPI);  cout << "Speaker enable with '1' " << endl       << "Speaker disable with '0' " << endl       << "End with ESC" << endl;  for (;;)  {    UINT key = bioskey(1);    if (key)    {      bioskey(0);      switch (key)      {      case 0x011b:        tim2gate=0;        tim2data=0;        return EXIT_SUCCESS;      case 0x0231:        tim2gate=1;        tim2data=1;        break;      case 0x0b30:        tim2gate=0;        tim2data=0;        break;      }      cout << tim2gate << ' ' << tim2data << endl;    }  }}.D.
Testen der BIT-Klassen HC04IO3.CPP
Die Klasse IO umfat sowohl die byteweise Ansprache als auch die bitweise Ansprache eines Ports. Analog zum vorigen Programm werden im folgenden Programm alle mglichen Bitoperationen getestet. Die Einklammerung der beiden Abschnitte entspricht dem Programm HC04IO1.CPP, lediglich die Zeile
    { IOBYTE_IO par1(PC_LPT1|LPT_DATA,0x55); }
wirkt etwas kurios. Beim Testen der Bitklasse kommt es nmlich darauf an, da bei den Bitoperationen der Portwert tatschlich unverndert bleibt, daher wird der Port auf einen Anfangswert eingestellt und am Ende des Tests wieder angezeigt. Die Elementfunktion set() setzt das Byte und die Funktion byte() liest den Portwert ab. 
.D.C:\MYLIB\SAMPLE\HC04IO3.CPP,/* HC04IO3.CPP *//* * Testen der Klassen IOBIT_OUT/IOBIT_IN * ===================================== */#include <portable.h>#include <iodef.h>#include <keyscan.h>#include <mylib.h>#include <io.hpp>#ifndef MYLIB#include "\mylib\source\io.cpp"#endif#define F hex<<setw(1)<<setfill('0')#define OUT(o,a,n) out(o,a.old(),n())#define OUTIO(o,io) out(o,io.old(),io.val())VOID out(CHAR* operation, UCHAR alt, UCHAR neu){  cout << operation <<F<< ' ' ;  cout << "alt:" <<F<< (INT)alt << ' '       << "neu:" <<F<< (INT)neu << ' ' <<endl;}INT main(VOID){  cout << endl       << "IOBIT_OUT/IOBIT_IN: Operatoren-Test\n"       << "Parallele Schnittstelle, Ende mit ESC"       <<endl;  IOBIT_IN biti(0,PC_LPT1|LPT_DATA);  IOBIT_OUT bito(0,PC_LPT1|LPT_DATA);  bito.set(0x55);  cout << "Oldbyte=" <<hex<< (UINT)bito.byte() <<endl;  cout << "Bittest IN<-OUT mit 1: <-"; cin.get();  bito=1;  OUT("bito=1:     ",bito,biti);  ++bito;  OUT("++bito:     ",bito,biti); bito=1;  --bito;  OUT("--bito:     ",bito,biti); bito=1;  ~bito;   OUT("~bito:      ",bito,biti); bito=1;  bito&=1; OUT("bito&=1:    ",bito,biti); bito=1;  bito&=0; OUT("bito&=0:    ",bito,biti); bito=1;  bito|=1; OUT("bito|=1:    ",bito,biti); bito=1;  bito|=0; OUT("bito|=0:    ",bito,biti); bito=1;  bito^=1; OUT("bito^=1:    ",bito,biti); bito=1;  bito^=0; OUT("bito^=0:    ",bito,biti); bito=1;  cout << "Newbyte=" <<hex<< (UINT)bito.byte() <<endl;  cout << "Fortsetzung IN<-OUT mit 0 <-:"; cin.get();  cout << "Oldbyte=" <<hex<< (UINT)bito.byte() <<endl;  bito=0;  OUT("bito=0:     ",bito,biti);  ++bito;  OUT("++bito:     ",bito,biti); bito=0;  --bito;  OUT("--bito:     ",bito,biti); bito=0;  ~bito;   OUT("~bito:      ",bito,biti); bito=0;  bito&=1; OUT("bito&=1:    ",bito,biti); bito=0;  bito&=0; OUT("bito&=0:    ",bito,biti); bito=0;  bito|=1; OUT("bito|=1:    ",bito,biti); bito=0;  bito|=0; OUT("bito|=0:    ",bito,biti); bito=0;  bito^=1; OUT("bito^=1:    ",bito,biti); bito=0;  bito^=0; OUT("bito^=0:    ",bito,biti); bito=0;  cout << "Newbyte=" <<hex<< (UINT)bito.byte() <<endl;  cout << "Bittest IN <-OUT beendet" <<endl;  cout << "Beginn IO-Test" <<endl; cin.get();  { IOBYTE_IO par1(PC_LPT1|LPT_DATA,0x55); }  IOBIT_IO bitio(0,PC_LPT1|LPT_DATA);  bitio();  cout << "Oldbyte=" <<hex<< (UINT)bitio.byte() <<endl;  cout << "Bittest IO mit 1: <-"; cin.get();  cout << "Newbyte=" <<hex<< (UINT)bitio.byte() <<endl;  bitio=1;  OUTIO("bitio=1:     ",bitio);  ++bitio;  OUTIO("++bitio:     ",bitio); bitio=1;  --bitio;  OUTIO("--bitio:     ",bitio); bitio=1;  ~bitio;   OUTIO("~bitio:      ",bitio); bitio=1;  bitio&=1; OUTIO("bitio&=1:    ",bitio); bitio=1;  bitio&=0; OUTIO("bitio&=0:    ",bitio); bitio=1;  bitio|=1; OUTIO("bitio|=1:    ",bitio); bitio=1;  bitio|=0; OUTIO("bitio|=0:    ",bitio); bitio=1;  bitio^=1; OUTIO("bitio^=1:    ",bitio); bitio=1;  bitio^=0; OUTIO("bitio^=0:    ",bitio); bitio=1;  cout << "Newbyte=" <<hex<< (UINT)bitio.byte() <<endl;  { IOBYTE_IO par1(PC_LPT1|LPT_DATA,0x55); }  cout << "Fortsetzung IO mit 0 <-:"; cin.get();  cout << "Oldbyte=" <<hex<< (UINT)bitio.byte() <<endl;  cout << "Newbyte=" <<hex<< (UINT)bitio.byte() <<endl;  bitio=0;  OUTIO("bitio=0:     ",bitio);  ++bitio;  OUTIO("++bitio:     ",bitio); bitio=0;  --bitio;  OUTIO("--bitio:     ",bitio); bitio=0;  ~bitio;   OUTIO("~bitio:      ",bitio); bitio=0;  bitio&=1; OUTIO("bitio&=1:    ",bitio); bitio=0;  bitio&=0; OUTIO("bitio&=0:    ",bitio); bitio=0;  bitio|=1; OUTIO("bitio|=1:    ",bitio); bitio=0;  bitio|=0; OUTIO("bitio|=0:    ",bitio); bitio=0;  bitio^=1; OUTIO("bitio^=1:    ",bitio); bitio=0;  bitio^=0; OUTIO("bitio^=0:    ",bitio); bitio=0;  cout << "Newbyte=" <<hex<< (UINT)bitio.byte() <<endl;  cout << "Bittest IO und IN<-OUT beendet,<-";  cin.get();  return EXIT_SUCCESS;}.D.
Jetzt ein paar ntzliche Anwednungen:
Anzeige und nderung des Zustandes von IO-Ports HC04IO2.CPP
Zu Testzwecken ist es wnschenswert, rasch ein Programm zur Hand zu haben, mit dem man eine oder mehrere Portadressen zyklisch ausliest und andere einstellt. Das folgende Programm erlaubt die Ausgabe und Einstellung des Inhalts von Portadressen durch Angabe der Adresse in der Kommandozeile. 
Programmaufruf: 
HC04IO2             Zeigt einen Hilfetext anHC04IO2 adr...      Anzeige des Inhalts einer IO-Adresse HC04IO2 adr:hh...   Setzen einer IO-Adresse HC04IO2 adr=hh...   Setzen und Anzeigen einer IO-Adresse
Es knnen beliebig viele Adressen angezeigt werden.
Das Pointerarray port[] enthlt Zeiger auf Objekte des Typs IOBYTE_IO. Die Funktion chk() prft fr jede Eingabe in der Kommandozeile das Eingabeformat und bricht im Fehlerfall mit einer Meldung das Programm ab. Ohne Fehler wird weiters geprft, ob der Port bereits im Portarray enthalten ist, man kann also nicht mehrmals dieselbe Adresse angeben. 
Die eigentliche Anzeigeroutine luft, bis eine Taste gedrckt wird. Da der Bildschirmaufbau die meiste Zeit in Anspruch nimmt, wird der Portwert nur bei nderungen auf den Bildschirm geschrieben. 
Bei diesem Programm zeigt sich auch eine Schwche der IO-Klassen: Es kann kein gemeinsamer Pointer gebildet werden, der sowohl Funktionen eines _IN-Objekts, eines _OUT-Objekts und eines _IO-Objekts aufruft, d.h. der Pointer kann schon gebildet werden aber die Funktionen sind nicht aufrufbar, da sie die Basisklasse nicht enthlt. Auch eine rein virtuelle Basisklasse mit virtuellen Elementfunktionen ist keine Lsung, da es abgeleitete Klassen gibt, die nur einen Teil der Elementfunktionen enthalten. Kompliziertere Konstruktionen sind aber aus Geschwindigkeitsgrnden nicht ratsam, soda einfach die universelle IOBYTE-IO-Klasse verwendet wird. 
.D.C:\MYLIB\SAMPLE\HC04IO2.CPP,/* HC04IO2.CPP *//*  * Anzeige und nderung des Zustandes von IO-Ports * =============================================== */#include <string.h>#include <stdlib.h>#include <mylib.h>#include <portable.h>#include <iodef.h>#include <keyscan.h>#include <io.hpp>#ifndef MYLIB#include "\mylib\source\io.cpp"#endifconst INT MAXPORT=255;
INT main(INT argc, CHAR * argv[]){  IOBYTE_IO *port[MAXPORT];  if (argc==1) // Hilfe-Anzeigen  {    cout << "\nAnzeige und nderung von IO-Ports\n";    cout <<   "=================================\n";    cout << "Aufruf: " << __FILE__         << "{adr|adr" << TRENN_OUT << "hh|adr"         << TRENN_IO << "hh} " << endl;    cout << "adr    ... drei HEX-Stellen \n";    cout << "hh     ... zwei HEX-Stellen \n";    cout << "adr    ... IN: zeigt Portwert adr an " ;    cout << DISP_IN << endl;    cout << "adr" << TRENN_OUT         << "hh ... OUT: ndert Portwert adr auf hh "         << DISP_OUT << endl;    cout << "adr" << TRENN_IO         << "hh ... IN_OUT: ndert Portwert adr auf hh"            "und zeigt Portwert an "         << DISP_IO << endl;    return EXIT_FAILURE;  }  for (INT i=1; i<argc; i++)  {    INT adr_t;    DIRECTION io_t;    UCHAR val_t;    if (chk(argv[i],adr_t,io_t,val_t,TRUE)==IO_NOERROR)    {      port[i-1] = new IOBYTE_IO(adr_t,val_t);      port[i]=NULL;      continue;    }    else      exit(EXIT_FAILURE);  }  { // testing multiple assignments    // ============================    BOOL exist[PC_MAXPORTS][2];    for (INT i=0; i<PC_MAXPORTS; i++)      for (INT j=0; j<2; j++)        exist[i][j]=FALSE;    IOBYTE_IO **iop=port;    while (*iop)    {      INT adr=(*iop)->adr();      DIRECTION io=(*iop)->io();      if (exist[adr][io])      {        cout << "\nDoppelbelegung bei: "             << hex << (*iop)->adr();        return EXIT_FAILURE;      }      else        exist[(*iop)->adr()][(*iop)->io()]=TRUE;      iop++;    }  }  constream con;  con.clrscr();  BOOL start=TRUE;  for (;;)  {    INT displayposition=0;    IOBYTE_IO **iop=port;    while (*iop)    {      if (kbhit())      {        UINT c = getch();        if (c==0x1b) return EXIT_SUCCESS;      }      UCHAR old_value = (**iop).old();      // Bildschirm nur bei nderungen      // und zu Beginn auffrischen      if ((old_value!=(**iop)()) || (start))      {        INT  char_position, row, column;        char_position=displayposition*8;        column = char_position % 80;        row = char_position / 80;        con << setxy(column+1,row+1) << (**iop);      }      iop++;      displayposition++;    }    start = FALSE;  }}.D.
Testen der parallelen Schnittstelle HC04PA1.CPP
Ein weiteres Programm, das die Bitklasse benutzt testet den parallelen Port mit einem Prfstecker. Die Belegung entnehme man dem Hilfetext am Programmanfang, den man erhlt, wenn man das Programm mit einem beliebigen Parameter aufruft. Der Hilfetext erklrt die Steckerbelegung, die Polaritt der Ein- und Ausgabe, die Position des Interruptpins und die Belegung des Prfsteckers. 
Parallele Schnittstelle=======================PORT+0                                              Bit      7     6     5     4     3     2     1     0Name    D7    D6    D5    D4    D3    D2    D1    D0Pin      9     8     7     6     5     4     3     2Logik    +     +     +     +     +     +     +     +E/A     IO    IO    IO    IO    IO    IO    IO    IOPORT+1                                              Bit      7     6     5     4     3     2     1     0Name  BUSY   ACK  PEND  SELE ERROR                  Pin     11    10    12    13    15                  Logik    -     +     +     +     -                  E/A      I     I(IRQ)I     I     I                  PORT+2                                              Bit      7     6     5     4     3     2     1     0Name                     IRQ    SI  INIT    AF STROBPin                             17    16    14     1Logik E                    +     +     +     -     -Logik A                    +     -     +     -     -E/A                       RW    IO    IO    IO    IOMasse: Pins 18-24if PORT+2,bit 4 == 1 --> INT=ON,                  Hi-to-Lo-Transition on Pin 10 causes IRQ          Parallel Testadapter connects pins 1-13,2-15,10-16,11-17,12-14 
Zuerst wird fr alle drei erlaubten Portadressen berprft, ob ein Port existiert, das erfolgt auf Grund der Rcklesbarkeit des Datenports durch lpt.tst() (Testfunktion aus IOBYTE_IO). 
Vorhandene Ports werden auf Grund der Verbindungen des Prfsteckers mit IOBIT_LOOP-Objekten getestet. Jedes IOBIT_LOOP-Objekt l1,l2,l3 und l4 entspricht einer Drahtbrcke am Teststecker. Als Argument wird jeweils die Adresse und die Bitnummer angegeben. Der letzte Parameter (TRUE oder FALSE) gibt die Polaritt der Verbindung an. Die Testfunktion tst() (hier jene der Klasse IOBIT_LOOP), die die Korrektheit der Verbindung feststellt. 
Abschlieend wird geprft, ob der Interrupt fr den betreffenden Port ein- oder ausgeschaltet ist. 
Die Testqualitt hngt wesentlich von der Funktion tst() in der Klasse IOBIT_IO ab. 
.D.C:\MYLIB\SAMPLE\HC04PA1.CPP,/* HC04PA1.CPP *//* * Prfen des parallelen Ports mit Teststecker * =========================================== */#include <iodef.h>#include <portable.h>#include <mytypes.h>#include <io.hpp>#ifndef MYLIB#include "\mylib\source\io.cpp"#endifconst INT NUMPORTS=3;UINT ports[NUMPORTS] = { PC_LPT1, PC_LPT2, PC_LPT3 };BOOL exist[NUMPORTS] = { FALSE, FALSE, FALSE };VOID main(INT argc){  cout << endl;  cout << "PARALLEL-PORTTEST\n";  cout << "=================\n";  if (argc>1)  {    cout << "Portadressen: ";    for (INT port=0; port<NUMPORTS; port++)    {      cout << hex << setw(3) << ports[port] << ", ";    }    cout << endl;    cout << // siehe nebenstehenden Kasten    return;  }  for (INT port=0; port<NUMPORTS; port++)  {    cout << "Port-Nr: " << port;    cout << "\nPortadresse: "         << hex << ports[port] << " : ";    IOBYTE_IO lpt(ports[port]);    cout << "Port " << port << endl;    if (lpt.tst(cout,TRUE)==0)    {      exist[port]=TRUE;    }    else    {      exist[port]=FALSE;      cout << " not";    }    cout << " found" << endl;  }  for (port=0; port<NUMPORTS; port++)  {    if (exist[port]==FALSE) continue;    INT error=0;    cout << "Testing port " << port         << " at " << hex << ports[port] << endl;    cout << "Insert test plug in port " << port         << " press a key" << endl;    cin.get();    IOBIT_LOOP      l1(ports[port]+2,0,ports[port]+1,4,FALSE);    if (l1.tst(cout)) error++;    IOBIT_LOOP      l2(ports[port],0,ports[port]+1,3,TRUE);    if (l2.tst(cout)) error++;    IOBIT_LOOP      l3(ports[port]+2,2,ports[port]+1,6,TRUE);    if (l3.tst(cout)) error++;    IOBIT_LOOP      l4(ports[port]+2,1,ports[port]+1,5,FALSE);    if (l4.tst(cout)) error++;    cout << error << " errors" << endl;    IOBIT_IO portbit(4,ports[port]+2);    cout << "Interrupt: "         << ((portbit())?"ON":"OFF") << endl;    cout << "Port ";    if (error)      cout << "failed";    else      cout << "passed";    cout << endl;  }}.D.
Testen der seriellen Schnittstelle HC04SE1.CPP
Dieser Test ist natrlich auch fr die serielle Schnittstelle durchfhrbar: 
Serielle Schnittstelle======================PORT-Adressen: 1:3f8 2:2f8 3:3e8 4:2e8PORT+0 3F8 (DLAB=1) Baudrate low BytePORT+1 3F9 (DLAB=1) Baudrate high Byte        PORT+1           PORT+0         f e d c b a 9 8  7 6 5 4 3 2 1 0    0 0 0 0 1 0 0 1  0 0 0 0 0 0 0 0    50    0 0 0 0 0 0 0 1  1 0 0 0 0 0 0 0   300    0 0 0 0 0 0 0 0  0 1 1 0 0 0 0 0  1200    0 0 0 0 0 0 0 0  0 0 1 1 0 0 0 0  2400    0 0 0 0 0 0 0 0  0 0 0 1 1 0 0 0  4800    0 0 0 0 0 0 0 0  0 0 0 0 1 1 0 0  9600    0 0 0 0 0 0 0 0  0 0 0 0 0 1 1 0 19200    0 0 0 0 0 0 0 0  0 0 0 0 0 0 1 1 38400    0 0 0 0 0 0 0 0  0 0 0 0 0 0 1 0 56000PORT+0 3F8 (DLAB=0) DATEN-RX/TXPORT+1 3F9 (DLAB=0) INTERRUPT-Enable    7     6     5     4     3     2     1     0    0     0     0     0     CTS   OR    TX    RX                            DTR   PE                             RI    FE                             DCD   BI           Prioritt:        1     2     3     4PORT+2 3fA INTERRUPT-Status    7     6     5     4     3     2     1     0    0     0     0     0     0     x     x     1 NO INT                                  1     1     0 RX-Line                                  1     0     0 RX-Data                                  0     1     0 TX-Data                                  0     0     0 Modemst.PORT+3 3FB LINE-Control    7     6     5     4     3     2     1     0                <--PARITY----->   STOPB DATABITS    DLAB        SP    0 ODD       0->1  0     0  5 Bits          1=TXOFF     1 EVEN      1->2  1     0  6 Bits          0=TXON            0 NO        0     1  7 Bits                            1 YES       1     1  8 BitsPORT+4 3FC MODEM-Control    7     6     5     4     3     2     1     0    0     0     0     LOOP  OUT2  OUT1  RTS   DTRPORT+5 3FD LINE-Status    7     6     5     4     3     2     1     0    0     TS    TX    BI    FE    PE    OR    RXPORT+6 3FE Modem-Status    7     6     5     4     3     2     1     0    DCD   RI    DSR   CTS   DDCD  DRI   DDSR  DCTSPinbelegung        TX   RX   DTR  DSR  RTS  CTS  DCD  RI   GND24-pol: 2    3    20   6    4    5    8    22   1,7 9-pol: 3    2    4    6    7    8    1    9    5Serieller Testadapter verbindet: 24-pol: 1-7, 2-3, 4-5-8, 6-20, 11-22, 15-17-23, 18-25 9-pol:      2-3, 1-7-8, 4-6                         
.D.C:\MYLIB\SAMPLE\HC04SE1.CPP,/* HC04SE1.CPP *//* * Testen der seriellen Schnittstelle * ================================== */#include <iodef.h>#include <portable.h>#include <mytypes.h>#include <io.hpp>#ifndef MYLIB#include "\mylib\source\io.cpp"#endifconst INT NUMPORTS=4;UINT ports[NUMPORTS] =  { PC_COM1, PC_COM2, PC_COM3, PC_COM4 };BOOL exist[NUMPORTS] =  { FALSE,   FALSE,   FALSE,   FALSE };INT error=0;VOID main(INT argc){  cout << endl;  cout << "SERIAL-PORTTEST\n";  cout << "===============\n";  if (argc>1)  {    cout << // siehe nebenstehender Kasten    exit(EXIT_FAILURE);  }  // hier fehlt Kode zur Portidentifikation  for (port=0; port<NUMPORTS; port++)  {    if (exist[port]==FALSE) continue;    cout << "Insert Test-Plug in port " << port	 << " and press a key!";    cin.get();    IOBYTE_IO data(ports[port]);    IOBIT_OUT dtr(0,ports[port]+4);    IOBIT_OUT rts(1,ports[port]+4);    IOBIT_IN dcd(7,ports[port]+6);    IOBIT_IN ri(6,ports[port]+6);    IOBIT_IN dsr(5,ports[port]+6);    IOBIT_IN cts(4,ports[port]+6);    IOBIT_LOOP l1(dtr,dsr,TRUE);    IOBIT_LOOP l2(rts,cts,TRUE);    INT error=0;    cout << "Testing port:" << port	 << " at " << hex << ports[port]	 << endl;    data = 0xff;    delay(100);    if (data()!=0xff)    {      error++;      cout << "OUT:0xff "	   << "IN:" << hex << (INT)data.val()	   << endl;    }    data = 0x00;    delay(100);    if (data()!=0x00)    {      error++;      cout << "OUT:0x00 "	   << "IN:" << hex << (INT)data.val()	   << endl;    }    if (l1.tst(cout))      error++;    if (l2.tst(cout))      error++;    cout << error << " errors" << endl;    cout << "Port " << port;    if (error)    {      cout << " failed";    }    else    {      cout << " passed";    }    cout << endl;  }  cout << "Press a key" << endl;  cin.get();}.D.
Wir beschlieen unseren heutigen, etwas lnger geratenen Abschnitt mit einem Programm zur Kontrolle von IO-Ports, welche auf hherer Ebene tatschlich nicht untersttzt werden: die Ports des CMOS-RAM (Es sei denn, man versteht das Setup-Programm zu Beginn als eine Art Untersttzung; hier ist der programmgesteuerte Zugriff gemeint). Diese Anwendung kann, unvorsichtig angewendet, Ihren AT oder 386 ganz schn durcheinanderbringen. Bevor wir das Programmprojekt angehen, mssen wir genaue Kenntnis ber die einzelnen Speicherbereiche des CMOS-RAM haben. Diese entnehmen wir TGM-DSK-140.
Anwendung: CMOS-RAM, HC04CM*.*
In jedem AT und allen Nachfolgern (386, 386SX und 486) wurde ein kleines 64/128-Byte groes batteriegepuffertes CMOS-RAM eingebaut, welches aber nicht direkt in die IO-Adressen gemappt ist (es wre ja kein Platz mehr dafr gewesen), sondern ber einen eigenen Mechanismus ber die Adressen 0x70(enthlt CMOS-RAM-Adresse), 0x71(enthlt CMOS-RAM-Daten) schreib- und lesbar ist. Neben der eigentlichen Uhr, die die CMOS-RAM-Adressen 0 bis 0xf belegt, sind die darauffolgenden Bytes 0x10 bis 0x2d fr die Rechnerkonfiguration reserviert. Der darauf folgende Speicherbereich ist im Prinzip frei es werden jedoch, je nach BIOS, dort Festplattentypen gespeichert, die im BIOS nicht von vornherein enthalten sind. 
Bedeutung der CMOS-Adressen00H-0dH used by real-time clock 0eH     POST diagnostics status byte 0fH     shutdown status byte 10H     diskette drive type      -----+ 11H     reserved                      | 12H     hard disk drive type          | 13H     reserved                      |=> checksum14H     equipment byte                |   15H-16H Base memory size              |   (10H-2dH) 17H-18H extended memory above 1M      | 19H     hard disk 1 type (if > 15)    | 1aH     hard disk 2 type (if > 15)    | 1bH-2dH reserved                 -----+ 2eH-2fH storage for checksum of CMOS addresses         10H through 2dH 30H-31H extended memory above 1M 32H     current century in BCD (eg, 19H) 33H     miscellaneous info. 34H-3fH reserved 
Die gewnschte CMOSRAM-Adresse wird in den IO-Port 0x70 geschrieben, die Daten knnen an Port 0x71 gelesen werden. Eine Ausgabe an Port 0x71 beschreibt diese CMOSRAM-Adresse. Fhren wir damit ein kleines Experiment aus:
Wir wollen das beschriebene Prinzip ausprobieren und das Jahrhundert-Byte auf CMOS-Adresse 32H auslesen, welches wir ziemlich sicher kennen. 
.D.C:\MYLIB\SAMPLE\HC04CM0.CPP,/* HC04CM0.CPP *//* * CMOS-RAM: Auslesen des Jahrhundert-Byte * ======================================= */#include <stdio.h>#include <dos.h>#include <mytypes.h>#include <portable.h>#define PC_CMOS_ADD 0x70#define PC_CMOS_DAT 0x71VOID main (VOID){  // Adresse des Jahrhundertbyte setzen  OUT_PORT(PC_CMOS_ADD,0x32);  INT century = IN_PORT(PC_CMOS_DAT);  cout << endl;  cout << "Wir haben jetzt das " << hex << century;  cout << "-te Jahrhundert, stimmts?" << endl;}.D.
Wenn wir Daten nicht nur lesen, sondern auch schreiben wollen, mssen wir den Sicherungsmechanismus beachten, der die Daten schtzt. Die Adressen 0x2e, 0x2f enthalten eine Prfsumme fr die Daten 0x10-0x2d, die bei einem Batterieausfall oder einer zuflligen Zerstrung der Daten falsch sein wird und daher den weiteren Betrieb des Rechners bis zur Behebung des Schadens verhindert. Im Normalfall ist das Konfigurationsprogramm im ROM fr die Vernderung der Bytes ausreichend. Wer allerdings mit gezielten Vernderungen experimentieren will oder den Speicherbereich bis zur CMOS-RAM-Adresse 0x7f nutzen will, der kann das folgende Patch-Programm als Ausgangspunkt benutzen. 
Das hier nicht dargestellte Programm HC04CM1.CPP benutzt nur die Makros IN_PORT und OUT_PORT zur Kommunikation mit dem CMOSRAM. Die Funktionen CMOSRAM_read(), CMOSRAM_write(), CMOSRAM_chksum() und CMOSRAM_printf() dienen zu Kommunikation mit dem CMOS-RAM. Die vier CMOSRAM-Funktionen ergnzen die entstehende Bibliothek MYLIB.LIB. 
HC04CM1A.CPP
Da die Ansprache des CMOS-RAM durchaus von allgemeiner Bedeutung sein kann, werden diese Funktionen nunmehr zu einer eigenen Klasse CMOSRAM zusammengefat, von der nur ein Objekt, hier cmosram gebildet werden kann. Das Programm zeigt nur mehr die Anwendung der Klasse, nicht aber den Kode der Elementfunktionen. 
.D.C:\MYLIB\SAMPLE\HC04CM1A.CPP,/* HC04CM1A.CPP *//* * Schreiben und Lesen des CMOS-RAM, mit Klasse CMOSRAM * ==================================================== */#include <cmosram.hpp>#ifndef MYLIB#include "\mylib\source\cmosram.cpp"#endifVOID main(VOID){  INT adr, checksum, value;  CMOSRAM cmosram;  constream con;  for (;;)  {    con.clrscr();    cmosram.print(con);    checksum=cmosram.chksum();    con << setxy(1,18);    con << "Checksum of adr 0x10..0x2d:";    con << setw(4) << hex << checksum;    do /* Read address to change */    {      con << setxy(1,19) << clreol;      con << "Alter byte (adr=0 ends) adr: ";      cin >> hex >> adr;      if (adr==0) return;    }    while ((adr<0x10) || (adr>0x7f));    con << clreol;    con << "value: ";    cin >> hex >> value;    cmosram.write(adr,value);    con << clreol;    // Update CHECKSUM if necessary    checksum=cmosram.chksum();    if (checksum != cmosram.chksum_read())      cmosram.chksum_write(checksum);  }}.D.
Um auch fr den Letztverbraucher anwendbar zu sein, ist das Programm noch ein bichen spartanisch, bentigt man doch zu seiner Bedienung die genaue Tabelle mit der Bedeutung der einzelnen Bytes. Daher: Schreiben Sie eine erweiterte Programmversion, welche die Inhalte genauer analysiert. Benutzen Sie dabei die folgende erweiterte Tabelle. Noch ein Vorschlag: Speichern Sie die aktuellen Inhalte des CMOS-RAM in eine Datei und ermglichen Sie den Vergleich mit dem aktuellen Zustand, etwa als Virusschutz!
CMOS-RAM im Detail

Addr Description  0   current second for real-time clock 1   alarm second 2   current minute for real-time clockrefer to    3   alarm minute   4   current minute for real-time clock  5   alarm hour  6   current day of week for real-time clock  7   current date of month for real-time clock  8   current month for real-time clock  9   current year for real-time clock 
0aH  RTC status register A      76543210                         Ľ        ͼ > rate selector (set to 0110)          > 22-stage divider (set to 010)       > Update in progress (UIP) flag.                          0 means OK to read. 
0bH  RTC status register B      76543210                    ҽ              > daylight savings enable.                   0=standard time (set to 0)             > 12 or 24-hr.                   0=12-hr mode (set to 1)            > BCD date mode. 1=binary, 0=BCD*                   (set to 0)           > Square wave enable.                     1=turn on square wave. (set to 0)          > enable update-ended                      int. 0 disables.                     (set to 0)         > enable alarm int. 0 disables                      (set to 0)                       See INT 1aH        > enable periodic int. 0 disables                        (set to 0)       > Update in progress (UIP) flag.                          0 means OK to read. 
0cH  RTC status register C.  Read-only interrupt status bits. 0dH  RTC status register D.      Bit 7=1 when a CMOS-RAM is receiving power           =0 to indicate a dead battery. 
0eH  POST diagnostics status byte      76543210            0 0      Ľ            > Time valid (After POST,                    1 means it's not Feb 30)           > Hard disk bad.                      1 means can't boot from hard disk          > RAM size not right.                      1 = POST found different RAM size         > Configuration record not right.                        1=different equipment        > Checksum invalid.                         1 means bad checksum in CMOS RAM       > Power Lost.                          1 means real-time clock battery died 
0fH  shutdown status byte
This byte is read upon startup after processor reset in order to determine if the reset was used as a way to get out of 80286 protected mode. 
         0 = soft reset (Ctrl-Alt-Del)              or unexpected shutdown          1 = shutdown after memory size is determined          2 = shutdown after memory test is performed          3 = shutdown after memory error              (parity check 1 or 2)          4 = shutdown with bootstrap loader request          5 = shutdown with FAR JMP              (restart int controller and              jmp to 0:[0467H])      6,7,8 = shutdown after passing a protected mode test          9 = shutdown after performing block move.              See INT 15H SubFn 87H        0aH = shutdown with FAR JMP              (immediate jmp to address at 0:[0467H]) 
10H  diskette drive type      76543210                          Ľ           0000 = no drive       ͼ > A: > 0001 = 360K drive          > B: ͼ   0000 = hi-capacity drive 
11H  reserved 
12H  hard disk drive type  (for drives C: and D:, when between 1 and 14)      76543210                          Ľ       ͼ > C: > 0000 = not present          > D: ͼ   else = type ID (below)                                  1111 = use Addr 19H/1aH 
These drive types are pre-defined by the ROM-BIOS. The vectors for INT 41H and INT 46H are initially set to a ROM table containing the information for the types of drive(s) installed.  See Hard Disk Parameter Table 
              Write   Land         Type Cyls Hds PreComp Zone Size              1   306  4   128     305   10M     2   615  4   300     615   21M     3   615  4   300     615   32M     4   940  8   512     940   65M     5   940  6   512     940   49M     6   615  4   0ffffH  733   21M     7   462  8   256     511   32M     8   733  5   0ffffH  733   31M     9   900 15   0ffffH  901  117M    10   820  3   0ffffH  820   21M 11   855  5   0ffffH  855   37M    12   855  7   0ffffH  855   52M    13   306  8   128     319   21M    14   733  7   0ffffH  733   44M    15   Use contents of byte 19 or 1A 16   612  4   0        663  21M 17   977  5   300      977  42M 18   977  7   0ffffH   977  59M 19  1024  7   512     1023  62M 20   733  5   300      732  31M 21   733  7   300      732  44M 22   733  5   300      733  31M 23   306  4   0        336  10M 24-47 reserved 
If the disk-type nibble is 0fH (15), then the disk type is stored in CMOS address 19H (drive C:) or address 1AH (drive D:) 
Notes:  Type 1 is the original XT hard disk         Type 2 is the standard 20M AT hard disk          (and a good first guess)         Types 16-23 were added to ROM-BIOS          dated 11/15/85 
13H  reserved 14H  Equipment byte      76543210      drvdspn/a7d      Ľ       ˼ ˼      > 1 = diskette drive(s) installed                 > 1 = 80287 co-processor installed           > prim. display 00 = none or <EGA>                                      01 = 40-clm CGA                                      10 = 80-clm CGA                                      11 = TTL Monochrome        > diskette drives                         (00=1, 01=2, 10=3, 11=4) 
15H  Base memory (low byte)   16H  Base memory (high byte)     0100H=256K, 0200H=512K, 0280H=640K17H  extended memory above 1M (low byte)18H  extended memory (high byte)             (in K bytes. 0-3c00H) See INT 15H SubFn 88H 19H  disk 0 (drive C:) hard disk type      if (CMOS addr 12H & 0fH) is 0fH 1aH  disk 1 (drive D:) hard disk type      if (CMOS addr 12H & f0H) is f0H 1bH-2dH reserved 2eH  checksum of CMOS addresses 10H through 2dH       (high byte) 2fH  (low byte) 30H  extended memory above 1M (low byte)31H  extended memory (high byte)             (in K bytes. 0-3c00H) See INT 15H SubFn 88H 32H  century in BCD (eg, 19H) 33H  miscellaneous info.       Bit 7=IBM 128K memory option installed            shadow-RAM is available     Bit 6=used by "Setup" utility            first bootup after running setup routine34H-3fH reserved.       Put your name here for everlasting amusement. 
Anmerkung: Vielen Programmierern ist zunchst die Benutzung von C++ noch fremd. Um den Umgang zu erleichtern sind viele der Beispielprogramme sowohl in einer C als auch in einer C++-Version enthalten. ABgedruckt wird jeweils die C++-Version. 
Teil5: Hauptspeicherzugriffe
Genauso, wie die Ein/Ausgabe in die Register der Peripheriebausteine, knnen wir den Speicher des PC lesen und schreiben. berlegen wir, wann wir wohin ungestraft zugreifen knnen (Lesen wird ja wohl berall erlaubt sein! oder?). Die Aufteilung des Hauptspeichers kennen wir schon aus unserem zweiten Teil (PC-NEWS-92/2, S.33). Unser Programm steht irgendwo in der TPA (Transient Program Area) aber wo?
Segmentadresse des eigenen Programms, HC05MM1.CPP
Innerhalb eines C-Programms erfolgt der Zugriff auf Speicheradressen ber dem Umweg ber Variablen, deren Adresse man eigentlich gar nicht kennt. Wie kann man nun die Adresse des eigenen Programms feststellen? Dazu exisitieren in jeder Sprache eigene, i.a. maschinenabhngige Verfahren. C bietet die Funktion segread() zur Feststellung der Segmentadressen an. 
Das folgende Programm zeigt den Wert der Segmentregister an. Achtung: Wenn man das Speichermodell unter 'Optionen' verndert, wird das Programm nicht nocheinmal kompiliert, da aus der Sicht der Kompilers das vorher kompilierte Programm durchaus noch gltig ist, daher nach dem ndern des Modells die .EXE-Datei lschen oder mit Bild All neu compilieren. 
.D.C:\MYLIB\SAMPLE\HC05MM1.CPP,/* HC05MM1.CPP *//* * Wo bin ich im Hauptspeicher ? * ============================= */#include <dos.h>#include <portable.h>#include <mytypes.h>VOID main(VOID){  struct SREGS s;  constream con;  con.clrscr();  con << "Wo bin ich, im Hauptspeicher?\n"	 "Versuchen Sie dieses Programm mit "	 "allen Speicher-Modellen auszufhren!\n";  segread(&s);  con << "CS: " << hex << s.cs << endl;  con << "DS: " << hex << s.ds << endl;  con << "ES: " << hex << s.es << endl;  con << "SS: " << hex << s.ss << endl;  cin.get();  con.clrscr();}.D.
Adresse einer Variablen
Man kann die Adresse ber den Adreoperator sichtbar machen. Bei den kleinen Kode-Speicher-Modellen (TINY, SMALL, COMPACT) ist aber diese Adresse nur der Offset, das zugehrige Datensegment mu mit segread(), wie im vorigen Beispiel gebildet werden.
Der genaue Wert einer Variablenadresse ist, wenn es sich um in C definierte Variable handelt meistens ohne Bedeutung. Anders ist es, wenn man auf genau definierten Adressen im Speicher, etwa im BIOS-Datenbereich oder im Bereich der Interrupt-Vektoren lesen oder schreiben will. 
Zugriff auf absolute Speicheradressen
Das in C verwendete Zugriffsverfahren auf Speicheradressen basiert auf der Anwendung von far-Zeigern mit 20-bit-Adresse, bestehend aus Segment und Offset, deren Ziel mit dem dafr vorgesehenen Makro MK_FP() eingestellt wird. Umgekehrt knnen Segment und Offset eines far-Zeigers mit den Makros FP_OFF() und FP_SEG() wieder zerlegt werden. 
Absolut adressierbare Speicherbereiche
Bevor wir das tun, sollten wir uns ber die Speicherbereiche informieren, die es gibt: PC-NEWS-92/2, S.33-34. Jetzt ist es an der Zeit diese auch detailierter zu behandeln. Zunchst sind da die 256 Interruptvektoren ab Adresse 0, jeweils 4 Bytes pro Vektor, daher 1kByte insgesamt. Jeder Vektor besteht aus 2 Bytes Offset und Segment, hier wieder immer niederwertiges Byte zuerst. Diese 256 Vektoren werden in einem anderen Teil unserer Folge behandelt. Nach den Interruptvektoren an der Adresse 00400=40:0=0:400 beginnt der 256 Byte groe BIOS-Datenbereich, der in seiner Position niemals verndert wird, daher auf absoluten Adressen immer ansprechbar ist (anders als Variable eines C-Programms, die ja immer andere Adressen aufweisen). 
BIOS
40:00  word    COM1 port address |   These addresses are zeroed out in the 40:02  word    COM2 port address |   OS/2 DOS Compatibility Box if any of 40:04  word    COM3 port address |   the OS/2 COM??.SYS drivers are loaded. 40:06  word    COM4 port address | 40:08  word    LPT1 port address 40:0A  word    LPT2 port address 40:0C  word    LPT3 port address 40:0E  word    LPT4 port address        (not valid in PS/2 machines) 40:0E  word    PS/2 pointer to 1k extended BIOS Data Area at top of RAM 
40:10  word    equipment flag (see int 11h)                bits:                0        1 if floppy drive present (see bits 6&7)  0 if not                1        1 if 80x87 installed  (not valid in PCjr)                2,3      system board RAM   (not used on AT or PS/2)                         00      16k                         01      32k                         10      48k                         11      64k                4,5      initial video mode                         00      no video adapter                         01      40 column color  (PCjr)                         10      80 column color                         11      MDA                6,7      number of diskette drives                         00      1 drive                         01      2 drives                         10      3 drives                         11      4 drives                8        0       DMA present                         1       DMA not present (PCjr)                9,A,B    number of RS232 serial ports                C        game adapter  (joystick)                         0       no game adapter                         1       if game adapter                D        serial printer (PCjr only)                         0       no printer                         1       serial printer present                E,F      number of parallel printers installed        note 1) The IBM PC and AT store the settings of the system board                switches or CMOS RAM setup information (as obtained by the BIOS                in the Power-On Self Test (POST)) at addresses 40:10h and                40:13h. 00000001b indicates "on", 00000000b is "off".             2) CMOS RAM map, PC/AT:               offset               contents                 00h         Seconds                 01h         Second Alarm                 02h         Minutes                 03h         Minute Alarm                 04h         Hours                 05h         Hour Alarm                 06h         Day of the Week                 07h         Day of the Month                 08h         Month                 09h         Year                 0Ah         Status Register A                 0Bh         Status Register B                 0Ch         Status Register C                 0Dh         Status Register D                 0Eh         Diagnostic Status Byte                 0Fh         Shutdown Status Byte                 10h         Disk Drive Type for Drives A: and B:                             The drive-type bytes use bits 0:3 for the first                             drive and 4:7 for the other                             Disk drive types:                             00h         no drive present                             01h         double sided 360k                             02h         high capacity (1.2 meg)                             03h-0Fh     reserved                 11h         (AT):Reserved    (PS/2):drive type for hard disk C:                 12h         (PS/2):drive type for hard disk D:                             (AT, XT/286):hard disk type for drives C: and D:                             Format of drive-type entry for AT, XT/286:                             0       number of cyls in drive (0-1023 allowed)                             2       number of heads per drive (0-15 allowed)                             3       starting reduced write compensation (not                                     used on AT)                             5       starting cylinder for write compensation                             7       max. ECC data burst length, XT only                             8       control byte                                     Bit                                     7       disable disk-access retries                                     6       disable ECC retries                                     5-4     reserved, set to zero                                     3       more than 8 heads                                     2-0     drive option on XT (not used by AT)                             9       timeout value for XT (not used by AT)                             12      landing zone cylinder number                             14      number of sectors per track (default 17,                                     0-17 allowed)                 13h         Reserved                 14h         Equipment Byte (corresponds to sw. 1 on PC and XT)                 15h-16h     Base Memory Size      (low,high)                 17h-18h     Expansion Memory Size (low,high)                 19h-20h     Reserved                             (PS/2) POS information Model 50 (60 and 80 use a 2k                             CMOS RAM that is not accessible through software)                 21h-2Dh     Reserved (not checksumed)                 2Eh-2Fh     Checksum of Bytes 10 Through 20  (low,high)                 30h-31h     Exp. Memory Size as Det. by POST (low,high)                 32h         Date Century Byte                 33h         Information Flags (set during power-on)                 34h-3Fh     Reserved             3) The alarm function is used to drive the BIOS wait function (int                15h function 90h).             4) To access the configuration RAM write the byte address (00-3Fh)                you need to access to I/O port 70h, then access the data via I/O                port 71h.             5) CMOS RAM chip is a Motorola 146818             6) The equipment byte is used to determine the configuration for the                power-on diagnostics.             7) Bytes 00-0Dh are defined by the chip for timing functions, bytes                0Eh-3Fh  are defined by IBM. 
40:12  byte    number of errors detected by infrared keyboard link (PCjr only) 40:13  word    availible memory size in Kbytes (less display RAM in PCjr)                this is the value returned by int 12h 
40:17  byte    keyboard flag byte 0 (see int 9h)                bit 7  insert mode on      3  alt pressed                    6  capslock on         2  ctrl pressed                    5  numlock on          1  left shift pressed                    4  scrollock on        0  right shift pressed 40:18  byte    keyboard flag byte 1 (see int 9h)                bit 7  insert pressed      3  ctrl-numlock (pause) toggled                    6  capslock pressed    2  PCjr keyboard click active                    5  numlock pressed     1  PCjr ctrl-alt-capslock held                    4  scrollock pressed   0 40:19  byte    storage for alternate keypad entry (not normally used) 40:1A  word    pointer to keyboard buffer head character 40:1C  word    pointer to keyboard buffer tail character 40:1E  32bytes 16 2-byte entries for keyboard circular buffer, read by int 16h 
40:3E  byte    drive seek status - if bit=0, next seek will recalibrate by                repositioning to Track 0.                bit 3  drive D          bit 2  drive C                    1  drive B              0  drive A 40:3F  byte    diskette motor status                bit 7  1, write in progress 3  1, D: motor on (floppy 3)                    6                       2  1, C: motor on (floppy 2)                    5                       1  1, B: motor on                    4                       0  1, A: motor on 40:40  byte    motor off counter                starts at 37 and is decremented 1 by each system clock tick.                motor is shut off when count = 0. 40:41  byte    status of last diskette operation     where:                bit 7 timeout failure            bit 3 DMA overrun                    6 seek failure                   2 sector not found                    5 controller failure             1 address not found                    4 CRC failure                    0 bad command 40:42  7 bytes NEC status 
40:49  byte    current CRT mode (hex value)                    00h 40x25 BW      (CGA)          01h 40x25 color   (CGA)                    02h 80x25 BW      (CGA)          03h 80x25 color   (CGA)                    04h 320x200 color (CGA)          05h 320x200 BW    (CGA)                    06h 640x200 BW    (CGA)          07h monochrome    (MDA)                extended video modes (EGA/MCGA/VGA or other)                    08h lores,16 color               09h med res,16 color                    0Ah hires,4 color                0Bh n/a                    0Ch med res,16 color             0Dh hires,16 color                    0Eh hires,4 color                0Fh hires,64 color 40:4A  word    number of columns on screen, coded as hex number of columns                20 col = 14h  (video mode 8, low resolution 160x200 CGA graphics)                40 col = 28h                80 col = 46h 40:4C  word    screen buffer length in bytes                (number of bytes used per screen page, varies with video mode) 40:4E  word    current screen buffer starting offset (active page) 40:50  8 words cursor position pages 1-8                the first byte of each word gives the column (0-19, 39, or 79)                the second byte gives the row (0-24) 40:60  byte    end line for cursor   (normally 1) 40:61  byte    start line for cursor (normally 0) 40:62  byte    current video page being displayed  (0-7) 40:63  word    base port address of 6845 CRT controller or equivalent                for active display           3B4h=mono, 3D4h=color 40:65  byte    current setting of the CRT mode register 40:66  byte    current palette mask setting  (CGA) 
40:67  5 bytes temporary storage for SS:SP during shutdown (cassette interface) 
40:6C  word    timer counter low word 40:6E  word    timer counter high word 
40:69  byte    HD_INSTALL (Columbia PCs) (not valid on most clone computers)                bit  0 = 0  8 inch external floppy drives                         1  5-1/4 external floppy drives                   1,2 =    highest drive address which int 13 will accept                            (since the floppy drives are assigned 0-3, subtract                            3 to obtain the number of hard disks installed) 
                  4,5 =    # of hard disks connected to expansion controller                   6,7 =    # of hard disks on motherboard controller                            (if bit 6 or 7 = 1, no A: floppy is present and                            the maximum number of floppies from int 11 is 3) 
40:70  byte    24 hour timer overflow 1 if timer went past midnight                it is reset to 0 each time it is read by int 1Ah 
40:71  byte    BIOS break flag (bit 7 = 1 means break key hit) 40:72  word    reset flag (1234 = soft reset, memory check will be bypassed)                PCjr keeps 1234h here for softboot when a cartridge is installed 
40:74  byte    status of last hard disk operation; PCjr special diskette control 40:75  byte    # of hard disks attached (0-2)    ; PCjr special diskette control 40:76  byte    hd control byte; temporary holding area for 6th param table entry 40:77  byte    port offset to current hd adapter ; PCjr special diskette control 
40:78  4 bytes timeout value for LPT1,LPT2,LPT3,LPT4 40:7C  4 bytes timeout value for COM1,COM2,COM3,COM4 (0-FFh seconds, default 1) 
40:80  word    pointer to start of circular keyboard buffer, default 03:1E 40:82  word    pointer to end of circular keyboard buffer, default 03:3E 
40:84  byte    rows on the screen (EGA only) 40:84  byte    PCjr interrupt flag; timer channel 0  (used by POST) 40:85  word    bytes per character (EGA only) 40:85  2 bytes (PCjr only) typamatic char to repeat 40:86  2 bytes (PCjr only) typamatic initial delay 40:87  byte    mode options (EGA only)                Bit 1   0 = EGA is connected to a color display                        1 = EGA is monochrome.                Bit 3   0 = EGA is the active display,                        1 = "other" display is active.                   Mode combinations:                   Bit3  Bit1     Meaning                     0     0   EGA is active display and is color                     0     1   EGA is active display and is monochrome                     1     0   EGA is not active, a mono card is active                     1     1   EGA is not active, a CGA is active 40:87  byte    (PCjr only) current Fn key code 40:88  byte    feature bit switches (EGA only) 0=on, 1=off                bit 3 = switch 4                bit 2 = switch 3                bit 1 = switch 2                bit 0 = switch 1 
40:88  byte    (PCjr only) special keyboard status byte                bit 7 function flag      3 typamatic (0=enable,1=disable)                    6 Fn-B break         2 typamatic speed (0=slow,1=fast)                    5 Fn pressed         1 extra delay bef.typamatic (0=enable)                    4 Fn lock            0 write char, typamatic delay elapsed 
40:89  byte    PCjr, current value of 6845 reg 2 (horiz.synch) used by                ctrl-alt-cursor screen positioning routine in ROM 40:8A  byte    PCjrCRT/CPU Page Register Image, default 3Fh 
40:8B  byte    last diskette data rate selected 40:8C  byte    hard disk status returned by controller 40:8D  byte    hard disk error returned by controller 40:8E  byte    hard disk interrupt (bit 7=working int) 40:90  4 bytes media state drive 0, 1, 2, 3 40:94  2 bytes track currently seeked to drive 0, 1 
40:96  byte    keyboard flag byte 3 (see int 9h) 40:97  byte    keyboard flag byte 2 (see int 9h) 
40:98  dword   pointer to users wait flag 40:9C  dword   users timeout value in microseconds 40:A0  byte    real time clock wait function in use 40:A1  byte    LAN A DMA channel flags 40:A2  2 bytes status LAN A 0,1 40:A4  dword   saved hard disk interrupt vector 40:A8  dword   EGA pointer to parameter table 40:B4  byte    keyboard NMI control flags (Convertible) 40:B5  dword   keyboard break pending flags (Convertible) 40:B9  byte    port 60 single byte queue (Convertible) 40:BA  byte    scan code of last key (Convertible) 40:BB  byte    pointer to NMI buffer head (Convertible) 40:BC  byte    pointer to NMI buffer tail (Convertible) 40:BD  16bytes NMI scan code buffer (Convertible) 40:CE  word    day counter (Convertible and after) to -04:8F               end of BIOS Data Area 40:90-40:EF             reserved by IBM 40:F0 16 bytes Intra-Application Communications Area (for use by applications 40:FF          to transfer data or parameters to each other) 
50:00  byte    DOS print screen status flag                         00h    not active or successful completion                         01h    print screen in progress                         0FFh   error during print screen operation 50:01          Used by BASIC 50:02-03       PCjr POST and diagnostics work area 50:04  byte    Single drive mode status byte                         00     logical drive A                         01     logical drive B 50:05-0E       PCjr POST and diagnostics work area 50:0F          BASIC: SHELL flag (set to 02h if there is a current SHELL) 50:10  word    BASIC: segment address storage (set with DEF SEG) 50:12  4 bytes BASIC: int 1Ch clock interrupt vector segment:offset storage 50:16  4 bytes BASIC: int 23h ctrl-break interrupt segment:offset storage 50:1A  4 bytes BASIC: int 24h disk error interrupt vector segment:offset storage 50:1B-1F       Used by BASIC for dynamic storage 50:20-21       Used by DOS for dynamic storage 50:22-2C       Used by DOS for diskette parameter table. See int 1Eh for values 50:30-33       Used by MODE command 50:34-FF       Unknown - Reserved for DOS 
0008:0047 IO.SYS or IBMBIO.COM IRET instruction. This is the dummy routine that           interrupts 01h, 03h, and 0Fh are initialized to during POST. 
Danach ist es zunchst einmal aus mit absolut spezifizierbaren Adreinhalten. Den folgenden Bereich, bi hinauf zur Adresse 09fff verwaltet das jeweilige Betriebssystem, in unserem Fall MSDOS. Danach folgen Video-Adapter mit wechselnden Inhalten, siehe Tabelle in Folge 2. 
Upper memory
Die folgenden Adressen sind jetzt nicht mehr so ganz verllich. 
C000:001E EGA BIOS signature (the letters IBM) 
F000:E05B loc ResetF000:E2C3 loc NMI Entry Point
Hard Disk Information Tables
Each sub-table contains a a set of 16 bytes for each particular disk type. Type number specified may differ with manufactures specification. The hard disk table shown here assumes the first entry is type 0. 
F000:E331 dw hdsk_cylinders Number of cylinders, hdsk_type_0F000:E333 db hdsk_heads Number of headsF000:E334 dw hdsk_lo_wrt_cyl Low write current cyl begin (XT)F000:E336 dw hdsk_precompcyl Write pre-compensation cylinderF000:E338 db hdsk_err_length Error correction burst length (XT)F000:E339 db hdsk_misl_bits
Miscellaneous bit functions:bits 0-2 disk option, XT only (XT)     0-2 unused, all others     3 = 1 if > 8 heads     4   unused     5 = 1 for bad map at last cylinder + 1     6 or 7 = 1 no retries
F000:E33A db hdsk_timeout Normal timeout  (XT)F000:E33B db hdsk_fmt_timout Format timeout  (XT)F000:E33C db hdsk_chk_timout Check timeout   (XT)F000:E33D dw hdsk_parkng_cyl Parking cylinder numberF000:E33F db hdsk_sectr_trac Number of sectors per trackF000:E340 db hdsk_unused UnusedF000:E331 ds hdsk_type_F000:E6F2 loc Bootstrap Load
System Configuration Table F000:E6F5 dw Config_tbl_size  Size of table in bytesF000:E6F7 db Config_model Model type  0F8h = 80386 model 70-80 types  0FCh = 80286 model 50-60 types, also most 80286/80386 compatibles  0FAh = 8088/86 model 25-30 typeF000:E6F8 db Config_sub_model Sub-Model type F000:E6F9 db Config_BIOS_rev BIOS revsion numberF000:E6FA db Config_features Feature information  bit 7=1, hard disk uses DMA 3  bit 6=1, dual interrupt chips  bit 5=1, has real-time-clock  bit 4=1, int 15h, ah=4Fh is supported (keyboard)  bit 3=1, external wait support  bit 2=1, has extended BIOS RAM  bit 1=1, micro-channel  bit 0=1, unusedF000:E6FB db Config_info_bytes Information bytes (future use)
Baud Rate TableTable of hex divsors  for the serial ports. Table divisors for bauds 110 to 19,200. F000:E729 dw baud_110, baud_rate_tblF000:E72B dw baud_150F000:E72D dw baud_300F000:E72F dw baud_600F000:E731 dw baud_1200F000:E733 dw baud_2400F000:E735 dw baud_4800F000:E737 dw baud_9600F000:E739 dw baud_19200F000:E82E loc Keyboard Function CallF000:E987 loc Keyboard Hardware InterruptF000:EC59 loc Floppy Disk Function CallF000:EF57 loc Floppy Disk ISR
Floppy Disk ParametersF000:EFC7 db dsk_info_1 Start of ROM BIOS data areas  hi nibble = stepping rate in ms  lo nibble = head unload time, msF000:EFC8 db  dsk_info_2 2nd info byte bit 0 = 0 for DMAF000:EFC9 db  dsk_motor_delay                    Delay after use for motor offF000:EFCA db  dsk_sectr_bytesBytes per sector  0 =  128 bytes                  1 =  256 bytes                  2 =  512 bytes                  3 = 1024 bytesF000:EFCB db dsk_sector_trac Number of sectors per trackF000:EFCC db dsk_head_gap Gap LengthF000:EFCD db dsk_data_length Data LengthF000:EFCE db dsk_format_gap Format Gap LengthF000:EFCF db dsk_format_byte Format write byteF000:EFD0 db dsk_settlg_time Head load time, in msF000:EFD1 db dsk_startup_tim Motor startup wait time, 125msF000:EFD2 LPT-Fuction-CallF000:F065 Video Function Call
Video Hardware RegistersF000:F0A4 db video_hdwr_tbl1 mode CGA 40 columns x 25 linesF000:F0B4 db video_hdrw_tbl2 mode CGA 80 columns x 25 linesF000:F0C4 db video_hdwr_tbl3 mode CGA graphicsF000:F0D4 db video_hdwr_tbl4 mode MDA 80 columns x 25 linesF000:F0E4 dw video_buf_size1 Video buffer bytes CGA 40x25F000:F0E6 dw video_buf_size2 Video buffer bytes CGA 80x25F000:F0E8 dw video_buf_size3 Video buffer bytes CGA GraphicsF000:F0EA dw video_buf_size4 Video buffer bytes CGA GraphicsF000:F0EC db video_columntbl Video columns per modes 0-7F000:F0F4 db video_hdwr_mode Video hardware modes (0-7)F000:F841 loc Memory size Function callF000:F84D loc Equipment Check Function callF000:F859 loc Cassette Function CallF000:FA6E db video_char_tbl Video characters in graphic modesF000:FE6E loc Timer Function  CallF000:FEA5 loc Timer Hardware InterruptF000:FEF3 dw int_vec_table Initial interrupt vectorsF000:FF1D dw int_data_tableF000:FF21 dw video_ptrF000:FF23 dw int_vec_table_2F000:FF53 loc Dummy Interrupt return
This routine processes invalid and unused interrupt requests. The hardware IRQ number is loaded into gen_int_occured, and the interrupt cleared. For software calls to an unused interrupt, a value 0FFh is loaded into gen_int_occured, and the routine returns to the caller without changing registers. Alternatively, some systems simply return (iret).
F000:FF54 loc Print Screen Function CallF000:FFF0 loc power_on_reset SYSTEM RESET
Reset the computer system. General operation includes a test of the CPU, ROM checksum, and initialization of hardware including:
Memory systemTimer/Counter (which is also used for RAM refresh)Interrupt Controller(s)DMA Controller(s)Keyboard ControllerVideo Controller & Video RAMFloppy ControllerHard Disk Controller (if present)
Portions of the hardware may also have specific tests made to insure reliable operation. Test failures may display error code on the screen if the video subsystem is operational, or generate beeps or LED blinks to signify the error.
Note: A soft reset uses the warm_boot_flag to skip the memory tests. (i.e. from pressing Ctrl-Alt-Del).
The system checks for installed ROMs by searching memory from C000h to the beginning of the BIOS, in 2K chunks. ROM memory is identified if it starts with the word AA55h. It is followed a one byte field length of the ROM (divided by 512). If ROM is found, the BIOS will call the ROM at an offset of 3 from the beginning. This feature was not supported in the earliest PC machines.
The last task turns control over to the bootstrap loader (assuming the floppy controller is operational)
F000:FFF5 BIOS release date F000:FFFE PC model identification              date      model byte            submodel byte    revision            04/24/81     FF = PC-0 (16k)         --              --            10/19/81     FF = PC-1 (64k)         --              --            08/16/82     FF = PC, XT, XT/370     --              --                              (256k motherboard)            10/27/82     FF = PC, XT, XT/370     --              --                              (256k motherboard)            11/08/82     FE = XT, Portable PC    --              --                              XT/370, 3270PC            01/10/86     FB = XT                 00              01            01/10/86     FB = XT-2 (early)            05/09/86     FB = XT-2 (640k)        00              02            06/01/83     FD = PCjr               --              --            01/10/84     FC = AT                 --              --            06/10/85     FC = AT                 00              01            11/15/85     FC = AT                 01              00            04/21/86     FC = XT/286             02              00            09/13/85     F9 = Convertible        00              00            09/02/86     FA = PS/2 Model 30      00              00            11/15/86     FC = AT, Enhanced 8mHz            02/13/87     FC = PS/2 Model 50      04              00            02/13/87     FC = PS/2 Model 60      05              00            1987         F8 = PS/2 Model 80      00              00                         2D = Compaq PC (4.77)   --              --                         9A = Compaq Plus (XT)   --              -- 
                  00FC 7531/2 Industrial AT              06FC 7552 Gearbox 
F000:FFFF db  model_sub_type
ROM-Scan 
During cold-boot, after the POST and installation of all default interrupt handlers, BIOS makes a check for external ROMs found on feature cards (boards installed in a PC slot).  This testing is informally called ROM-scan. 
Note: ROM-scan was NOT implemented in the earliest BIOS and the oldest PCs (those with a maximum of 64K on the motherboard) will not have this feature unless a later version of the ROM-BIOS has been installed. 
External ROM modules may be present between addresses c800:0000 and e000:0000. Each 2K block in this range is checked for a signature and is in this format: 
Offset Size Contents                Ŀ  +0      1  55H         Signature of BIOS-accessible ROM module             Ĵ         (first word in segment is aa55H)  +1      1  aaH             Ĵ  +2      1  len         length of ROM module in 512-byte increments               Ŀ  +3      ?            executable code                (often a NEAR jump to initialization code)             |   |         (a dummy byte usually exists to validate checksum) 
When BIOS finds a ROM signature, it performs a checksum on the defined module. Each byte is summed modulo 100H and the sum must be exactly 0.  When a module is verified to be valid, BIOS performs a FAR CALL to offset 0003H of its segment and the ROM must eventually return to the BIOS via a FAR RET. 
Typically, the ROM module will perform any hardware initialization tasks necessary and insert its own address into one or more interrupt vectors. 
The ROM sockets U17 and U37 on the AT motherboard are addressed starting at e000:0000.  A scan occurs in 64K blocks for a valid module.  If present, a module will have the aa55H signature, an unused length byte, executable code starting at xxxx:0003 and a checksum of 0 modulo 100H at e000:ffff. 
Lesen aus dem Hauptspeicher, HC05MM2.CPP
Das folgende Beispiel zeigt, wie man aus dem Bildschirmspeicher liest, wie man sich den Wert eines Interruptvektors ansieht und wie man die Adresse der seriellen Schnittstelle erfhrt. 
.D.C:\MYLIB\SAMPLE\HC05MM2.CPP,/* HC05MM2.CPP *//* * Lesen im Hauptspeicher * ====================== */#include <dos.h>#include <constream.h>#include <mytypes.h>#include <portptr.h>VOID main(VOID){  UINT far * uifp;  UINT seg,off,ser;  constream con;  con.clrscr();  con << "Lesen innerhalb des Hauptspeichers\n";  uifp = (UINT far *)MK_FP(0xb800,0x0000);  con << "Zeichen links oben am Bildschirm: "      << hex << setw(2) << (UINT)*uifp      << setw(1) << (UCHAR)*uifp      << "  Attribut: "      << hex << setw(2) << (UINT)((*uifp)>>8)      << endl;  uifp = (UINT far *)MK_FP(0x0000,5*4);  off = *uifp;  uifp++;  seg = *uifp;  con << "Print-Screen Interruptvektor: "      << hex << setw(4) << seg << off << endl;  uifp = (UINT far *)MK_FP(0x0040,0x0000);  ser = *uifp;  con << "Adresse der seriellen Schnittstelle: "      << hex << setw(4) << ser      << endl;  con << "Ende mit Taste";  cin.get();}.D.
Fragen: 
Was ist das Gegenteil eines far-Zeigers?
Wenn ein Zeiger nicht als far oder near deklariert wurde, welche Lnge hat er dann?
Uhrzeit aus dem Speicher, HC05MM3.CPP, HC05MM3A.CPP
Jeder einlangende Timer-Interrupt zhlt die Speicherstelle 0040:006c um 1 hoch, beginnt dabei um Mitternacht mit 0. Daherhat man auf 6c und den folgenden drei Adressen eine Zahl vom C-typ long, die eine Mazahl fr die aktuelle Uhrzeit ist. Die Einheit ist 53ms.
.D.C:\MYLIB\SAMPLE\HC05MM3.CPP,/* HC05MM3.CPP *//* * Lesen der Uhrzeit aus den BIOS-Daten * ==================================== */#include <dos.h>#include <constrea.h>#include <mytypes.h>VOID main(VOID){  ULONG far *time_p;  constream con;  con.clrscr();  cout << "Uhrzeit aus dem BIOS-Datenbereich\n";  time_p = (ULONG far *)MK_FP(0x0040,0x006c);  do  {    cout << *time_p << ' ';    delay (1000);  }  while (kbhit()==0);  cin.get();}.D.
Natrlich kann die Uhrzeit auch gleich richtig lesbar ausgegeben werden:
.D.C:\MYLIB\SAMPLE\HC05MM3A.CPP,/* HC05MM3A.CPP *//* * Lesen der Uhrzeit aus den BIOS-Daten * ==================================== */#include <dos.h>#include <portstrm.h>#include <mytypes.h>VOID main(VOID){  ULONG far *time_p;  UINT s;  constream con;  con.clrscr();  cout << "Uhrzeit aus dem BIOS-Datenbereich\n";  time_p = (ULONG far*)MK_FP(0x0040,0x006c);  do  {    s = (UINT)((FLOAT)(*time_p)*0.053);    cout << s/3600 << ':'         << s/60 << ':'         << s%3600 << ' ';    delay (1000);  }  while (kbhit()==0);  cin.get();}.D.
So wie diese gezeigten Beispiele gibt es viele Adressen, deren Wert uns Aufschlu ber den Zustand des Rechners gibt. 
Darber wollen wir uns jetzt eine bersicht verschaffen: 
Nachdem wir einige dieser Adressen exemplarisch analysiert haben, schreiben wir zwei Hilfsprogramme, die eine vollstndige, wenn auch nicht dechifrierte, Liste der BIOS-Variablen am Bildschirm ausgeben. 
Liste der Interruptvektoren, HC05MM4.CPP
.D.C:\MYLIB\SAMPLE\HC05MM4.CPP,/* HC05MM4.C *//* * Anzeige der Interruptvektoren * ============================= */#include <portstrm.h>#include <dos.h>#include <mytypes.h>#include <portptr.h>#define COLW 13#define COL 4#define ROW 16VOID main(VOID){  INT num, row, col;  ULONG far * ip;  constream con;  con.clrscr();  con << "Interruptvektoren\n";  ip=(ULONG far *)MK_FP(0,0);  for (num=0; num<0x100; num++)  {    col=(num/ROW)%COL; row=num%ROW;    con << setxy(1+col*COLW,row+2) << hex << setw(2)	<< num << ':'	<< setw(8) <<setfill('0') << *ip;    ip++;    if ((col==(COL-1)) && (row==(ROW-1)))    {      con << setxy(1,wherey()+1) << "Taste";      cin.get();    }  }}.D.
Anzeige der Interruptvektoren, HC05MM4A.C
.D.C:\MYLIB\SAMPLE\HC05MM4A.C,/* HC05MM4A.C *//*  * Liste der Interruptvektoren * =========================== */#include <stdlib.h>#include <stdio.h>void main (int argc,char *argv[]){  int far *p;  int i,i0,off,seg;  if (argc==1) i0=0;  else i0=atoi(argv[1]);  printf ("\n\n\nListe der Interrupt-Vektoren\n");  printf ("============================\n");  printf ("Aufruf: intvek      "          "Die ersten 64 Vektoren \n" \	  "        intvek iii  "          "Die Vektoren beginnend bei iii\n\n");  for (i=i0;i<i0+64;i++)  {    p=(int far *)(long)(i*4);    off=*p;p++;seg=*p;p++;    printf ("%2x %3i %4x:%4x",i,i,seg,off);    if ((i+1)%4) printf ("  "); else printf ("\n");  }  printf ("\n");}.D.
Verzeichnis der BIOS-Variablen, HC05MM5.CPP
.D.C:\MYLIB\SAMPLE\HC05MM5.CPP,/* HC05MM5.CPP *//* * Anzeige der BIOS-Daten * ====================== */#include <portstrm.h>#include <dos.h>#include <mytypes.h>#include <portptr.h>#define COLW 4#define COL 16#define ROW 16VOID main(VOID){  INT off, row, col;  UCHAR far * ip;  constream con;  con.clrscr();  con << "BIOS-Daten\n";  ip=(UCHAR far *)MK_FP(0x40,0);  for (off=0; off<0x10; off++) // niederwertiges byte  {    con << setxy(1,off+4) << hex << setfill('0')	<< off << " |";  }  for (off=0; off<0x10; off++) // hherwertiges byte  {    con << setxy((off*COLW)+5,2)        << hex << setfill('0') << off << " |";    con << setxy((off*COLW)+5,3) << "----";  }  for (off=0; off<0x100; off++)  {    col=(off/ROW)%COL;    row=off%ROW;    con << setxy(5+col*COLW,row+4)        << hex << setfill('0') << setw(2)	<< (UINT)*ip;    ip++;  }  con << setxy(1,wherey()+1);  con << "Taste";  cin.get();}.D.
Nachdem wir jetzt die Namen der Interruptvektoren kennen, ihre Adresse, dann auch eine Liste der BIOS-Variablen kennen, und auch ein geeignetes Ausdruckprogramm dafr besitzen, wre es eine lohnende Aufgabe, die Daten auch mnemonisch auszuwerten, hnlich, wie es die bekannten Analyseprogramme, wie MFT (=Manifest/Quarterdek), SYSINFO (=System Information/Norton), MSD (=Microsoft Diagnostics/Microsoft) oder CHECKIT() tun. Gefragt ist also eine Liste der Interruptvektoren, wobei als Zusatzinformation eine leicht erkennbare Kurzbezeichnung mitangegeben wird. Genauso soll eine BIOS-Datenliste ausgegeben werden, wobei die Daten ihrer korrekten Lnge und Gewichtung entsprechend gereiht werden. 
Austausch der seriellen und parallelen Ports, HC05MM6.C
.D.C:\MYLIB\SAMPLE\HC05MM6.C,/* HC05MM6.C *//* * Austausch serieller und paralleler Ports * ======================================== */#include <string.h>#include <stdio.h>#include <dos.h>#include <mytypes.h>VOID swap(UINT far *i, UINT far *j);VOID display_status(VOID);VOID error(CHAR *txt);CHAR *errtxt[] = {  "Invalid number of arguments",  "Incorrect length in argument 1",  "Incorrect length in argument 2",  "Arguments must have equal type",  "Port-number of arg1 must be in the Range of 1..4",  "Port-number of arg2 must be in the Range of 1..4",  "There is nothing to do",  "Port-number of arg1 must be in the Range of 1..3",  "Port-number of arg2 must be in the Range of 1..3",  "There is nothing to do",  "Only LPTx and COMx are allowed as arg1",    "\nExchange of Ports\n"    "=================\n"    "Usage:\n"    "portex                          Status-Display\n"    "portex ?                        This display\n"    "portex LPTx LPTy  x,y={1,2,3}   Exchange LPTs \n"    "portex COMx COMy  x,y={1,2,3,4} Exchange COMs \n" };VOID main(INT argc, CHAR * argv[]){  UINT far *fp;  UINT far *fq;  if (argc==1) // Status-Anzeige der Ports  {    display_status();    return;  }  else if ((argc==2) && (argv[1][0]=='?'))  {  }  else if (argc!=3)  {    error(errtxt[0]);  }  else if (strlen(argv[1])!=4)  {    error(errtxt[1]);  }  else if (strlen(argv[2])!=4)  {    error(errtxt[2]);  }  else if (strnicmp(argv[1],argv[2],3)!=0)  {    error(errtxt[3]);  }  else if (strnicmp(argv[1],"COM",3)==0) // change COM-Ports  {    if ((argv[1][3]<'1') || (argv[1][3]>'4'))    {      error(errtxt[4]);    }    else if ((argv[2][3]<'1') || (argv[2][3]>'4'))    {      error(errtxt[5]);    }    else if (argv[1][3]==argv[2][3])    {      error(errtxt[6]);    }    else    {      fp=(UINT far *)MK_FP(0x0040,(argv[1][3]-'1')*2);      fq=(UINT far *)MK_FP(0x0040,(argv[2][3]-'1')*2);      printf("Before: ");      display_status();      swap(fp,fq);      printf("After:  ");      display_status();      return;    }  }  else if (strnicmp(argv[1],"LPT",3)==0) // change LPT-Ports  {    if ((argv[1][3]<'1') || (argv[1][3]>'3'))    {      error(errtxt[7]);    }    else if ((argv[2][3]<'1') || (argv[2][3]>'3'))    {      error(errtxt[8]);    }    else if (argv[1][3]==argv[2][3])    {      error(errtxt[9]);    }    else    {      fp=(UINT far *)MK_FP(0x0040,(argv[1][3]-'1')*2 + 8);      fq=(UINT far *)MK_FP(0x0040,(argv[2][3]-'1')*2 + 8);      printf("Before: ");      display_status();      swap(fp,fq);      printf("After:  ");      display_status();      return;    }  }  else  {    error(errtxt[10]);  }  error(errtxt[11]);}VOID swap(UINT far *i, UINT far *j){  UINT temp;  temp=*j; *j=*i; *i=temp;}VOID display_status(VOID){  UINT far *fp;  CHAR * txt[] = { "COM1","COM2","COM3","COM4",                   "LPT1","LPT2","LPT3" };  INT i;  for (i=0; i<7; i++)  {    fp=(UINT far *)MK_FP(0x0040,i*2);    if (*fp)      printf("%s:%3x ",txt[i],*fp);    else      printf("%s:--- ",txt[i],*fp);  }  printf("\n");}VOID error(CHAR *txt){  printf("%s!\n\n",txt);}.D.
Die Liste der ntzlichen Programme, die die absoluten Speicheradressen benutzen knnte noch weiter fortgesetzt werden. Wir wollen uns aber jetzt einen allgemeineren Standpunkt fr den Speicherzugriff berlegen. 
Klassen M,MEM,BIOS,IBIOS
Der Speicherzugriff erfolgt immer auf folgende Weise: 
Pointer generieren	TYP far * t = 	(TYP far *)MK_FP(seg,off);Wert bearbeiten	*t=5;
Nachteile: (1) man mu mit den dereferenzierten Werten arbeiten (*t), es gibt keine Variable fr diesen Speicherplatz. (2) der Pointer ist gegen Vernderungen nicht abgesichert. 
C++ bietet die Mglichkeit, in einer eigenen Memory-Klasse absolute Speichervariable zu verwalten. Wie, entnehmen Sie aus der folgenden Headerdatei memabs.h, die auch den gesamten Kode innerhalb der Klasse enthlt. 
Klassenbaum
MMEM    MEM_P            BIOS              IBIOS
Die Inhalte der auf absoluten Speicheradressen abgelegten Variablen haben verschiedene Lnge und verschiedene Bedeutung. Im Terminus von C sind es die Typen char, int, long sowie pointer auf diese Typen oder Arrays davon. Eine Klasse im herkmmlichen Sinn, die einen solchen Speicherplatz beschreibt, htte den Nachteil, da sie fr die anderen Typen nocheinmal formuliert werden mte, obwohl die Inhalte der Klasse im Prinzip dieselben wren. Die neue 'Ausbaustufe' von C++, Version 2.1 von ATT, bietet dafr Abhilfe in Form sogenannter 'templates', das sind Klassen oder Funktione, bei denen ein oder mehrere Variablentypen noch nicht feststehen aber der Kode typenunabhngig verfat werden kann. Diese Konzeption bietet sich hier an. Die Basisklasse M 
MEMABS.H
.D.C:\MYLIB\INCLUDE\MEMABS.HPP,#ifndef __MEMABS_HPP#define __MEMABS_HPP#define P_POINTER#include <portable.h>#include <mytypes.h>#define GETMEM() Val=operator()()#define SETMEM() operator=(Val);return Valtemplate <class T>class MEM{protected:  T Val;  T far *p;public:  MEM(T far *tp)    { p=tp; operator()(); }  MEM(UINT s, UINT o)    { p=(T far *)MK_FP(s,o); operator()(); }  MEM(T far *tp, T t)    { p=tp; operator=(t); }  MEM(UINT s, UINT o, T t)    { p=(T far *)MK_FP(s,o); operator=(t); }  virtual VOID operator = (T t)    { Val = *p = t; }  virtual T operator () ()    { Val = *p; return Val; }  T operator ++ ()    { GETMEM(); Val++; SETMEM(); }  T operator -- ()    { GETMEM(); Val--; SETMEM(); }  T operator << (UINT i)    { GETMEM(); Val<<=i; SETMEM(); }  T operator >> (UINT i)    { GETMEM(); Val>>=i; SETMEM(); }  T operator ~ ()    { GETMEM(); Val=~Val; SETMEM(); }  T operator += (T t)    { GETMEM(); Val+=t; SETMEM(); }  T operator -= (T t)    { GETMEM(); Val-=t; SETMEM(); }  T operator *= (T t)    { GETMEM(); Val*=t; SETMEM(); }  T operator /= (T t)    { GETMEM(); Val/=t; SETMEM(); }  T operator %= (T t)    { GETMEM(); Val%=t; SETMEM(); }  T operator &= (T t)    { GETMEM(); Val&=t; SETMEM(); }  T operator |= (T t)    { GETMEM(); Val|=t; SETMEM(); }  T operator ^= (T t)    { GETMEM(); Val^=t; SETMEM(); }  T val() { return Val; }  T far *addr() { return p; }  UINT seg() { return FP_SEG(p); }  UINT off() { return FP_OFF(p); }};template <class T>class MEM_P : public MEM<T>{public:  MEM_P(UINT s, UINT o) : MEM<T>(s,o)    { Old = MEM<T>::operator()(); }  MEM_P(UINT seg, UINT off, T t) : MEM<T>(seg,off,t)    { MEM<T>::operator=(t); Old = t; }  VOID operator = (T t)   { Old = Val;     **((T far **)p) = t;     Val = t;   }  T operator () ()    { Old = Val;      Val = **((T far **)p);      return Val;    }  BOOL changed()    { return (Old==Val) ? FALSE : TRUE; }  UINT pointoff()    { return FP_OFF((T far *)(*p)); }  UINT pointseg()    { return FP_SEG((T far *)(p)); }  T far *pointaddr()    { return (T far *)(*p); }protected:  T Old;};#endif // __cplusplustypedef enum BIOSA{  rs232_port_1       = 0x4100, // UINT  rs232_port_2       = 0x4102, // UINT  rs232_port_3       = 0x4104, // UINT  rs232_port_4       = 0x4106, // UINT  prn_port_1         = 0x4108, // UINT  prn_port_2         = 0x410A, // UINT  prn_port_3         = 0x410C, // UINT  BIOS_data_seg      = 0x410E, // UINT  equip_bits         = 0x4110, // UINT  init_test_flag     = 0x4212, // UCHAR  main_ram_size      = 0x4113, // UINT  chan_io_size       = 0x4115, // UINT  keybd_flags_1      = 0x4217, // UCHAR  keybd_flags_2      = 0x4218, // UCHAR  keybd_alt_num      = 0x4219, // UCHAR  keybd_q_head       = 0x411A, // UINT  keybd_q_tail       = 0x411C, // UINT  keybd_queue        = 0x491E, // UINT  dsk_recal_stat     = 0x423E, // UCHAR  dsk_motor_stat     = 0x423F, // UCHAR  dsk_motor_tmr      = 0x4240, // UCHAR  dsk_ret_code       = 0x4241, // UCHAR  dsk_status_1       = 0x4242, // UCHAR  dsk_status_2       = 0x4243, // UCHAR  dsk_status_3       = 0x4244, // UCHAR  dsk_status_4       = 0x4245, // UCHAR  dsk_status_5       = 0x4246, // UCHAR  dsk_status_6       = 0x4247, // UCHAR  dsk_status_7       = 0x4248, // UCHAR  video_mode         = 0x4249, // UCHAR  video_columns      = 0x414A, // UINT  video_buf_siz      = 0x414C, // UINT  video_segment      = 0x414E, // UINT  vid_curs_pos0      = 0x4150, // UINT  vid_curs_pos1      = 0x4152, // UINT  vid_curs_pos2      = 0x4154, // UINT  vid_curs_pos3      = 0x4156, // UINT  vid_curs_pos4      = 0x4158, // UINT  vid_curs_pos5      = 0x415A, // UINT  vid_curs_pos6      = 0x415C, // UINT  vid_curs_pos7      = 0x415E, // UINT  vid_curs_mode      = 0x4160, // UINT  video_page         = 0x4262, // UCHAR  video_port         = 0x4163, // UINT  video_mode_reg     = 0x4265, // UCHAR  video_color        = 0x4266, // UCHAR  gen_io_ptr         = 0x4167, // UINT  gen_io_seg         = 0x4169, // UINT  gen_int_occured    = 0x426B, // UCHAR  timer_low          = 0x416C, // UINT  timer_hi           = 0x416E, // UINT  timer_rolled       = 0x4270, // UCHAR  keybd_break        = 0x4271, // UCHAR  warm_boot_flag     = 0x4172, // UINT  hdsk_status_1      = 0x4274, // UCHAR  hdsk_count         = 0x4275, // UCHAR  hdsk_head_ctrl     = 0x4276, // UCHAR  hdsk_ctrl_port     = 0x4277, // UCHAR  prn_timeout_1      = 0x4278, // UCHAR  prn_timeout_2      = 0x4279, // UCHAR  prn_timeout_3      = 0x427A, // UCHAR  prn_timeout_4      = 0x427B, // UCHAR  rs232_timeout_1    = 0x427C, // UCHAR  rs232_timeout_2    = 0x427D, // UCHAR  rs232_timeout_3    = 0x427E, // UCHAR  rs232_timeout_4    = 0x427F, // UCHAR  keybd_begin        = 0x4180, // UINT  keybd_end          = 0x4182, // UINT  video_rows         = 0x4284, // UCHAR  video_pixels       = 0x4185, // UINT  video_options      = 0x4287, // UCHAR  video_switches     = 0x4288, // UCHAR  video_1_reservd    = 0x4289, // UCHAR  video_2_reservd    = 0x428A, // UCHAR  dsk_data_rate      = 0x428B, // UCHAR  hdsk_status_2      = 0x428C, // UCHAR  hdsk_error         = 0x428D, // UCHAR  hdsk_int_flags     = 0x428E, // UCHAR  hdsk_options       = 0x428F, // UCHAR  hdsk0_media_st     = 0x4290, // UCHAR  hdsk1_media_st     = 0x4291, // UCHAR  hdsk0_start_st     = 0x4292, // UCHAR  hdsk1_start_st     = 0x4293, // UCHAR  hdsk0_cylinder     = 0x4294, // UCHAR  hdsk1_cylinder     = 0x4295, // UCHAR  keybd_flags_3      = 0x4296, // UCHAR  keybd_flags_4      = 0x4297, // UCHAR  timer_wait_off     = 0x4198, // UINT  timer_wait_seg     = 0x419A, // UINT  timer_clk_low      = 0x419C, // UINT  timer_clk_hi       = 0x419E, // UINT  timer_clk_flag     = 0x42A0, // UCHAR  lan_1              = 0x42A1, // UCHAR  lan_2              = 0x42A2, // UCHAR  lan_3              = 0x42A3, // UCHAR  lan_4              = 0x42A4, // UCHAR  lan_5              = 0x42A5, // UCHAR  lan_6              = 0x42A6, // UCHAR  lan_7              = 0x42A7, // UCHAR  video_sav_tbls     = 0x44A8, // ULONG  days_since_1_80    = 0x41CE, // UINT  prn_scrn_stat      = 0x5200  // UCHAR};/* BIOS_FP * ======= * creates VOID-far-Pointer to BIOS-Data-area * using enum BIOSA */#define BIOSA_FP(l) MK_FP(((l)&0xf000)>>8,(l)&0x00ff)/* MKB_FP * ====== * creates VOID-far-Pointer to BIOS-Data-area * using integer-argument */#define MKB_FP(l) MK_FP(0x40,(l)&0x00ff)/* BIOS_OFF * ======= * returns Offset of BIOS data * using enum BIOSA */#define BIOS_OFF(l) ((l)&0x00ff)#ifdef __cplusplus/* class BIOS * ========== * holds Pointer and Val of BIOS-Data-address * usable for all non-Pointer types */template <class T>class BIOS : public MEM<T>{public:  BIOS(UINT offset)  : MEM<T>(0x40,offset)    { Old=Val; }  BIOS(BIOSA offset)  : MEM<T>((offset>>8)&0xf0,offset&0x00ff)    { Old=Val; }  BIOS(UINT offset,T t)  : MEM<T>(0x40,offset,t)    { Old=Val; }  BIOS(BIOSA offset,T t)  : MEM<T>((offset>>8)&0xf0,offset&0x00ff,t)    { Old=Val; }  virtual T operator () ()    { Old=Val; return MEM<T>::operator()(); }  virtual VOID operator = (T t)    { Old=Val; MEM<T>::operator=(t); }  BOOL changed()    { return (Old==Val) ? FALSE : TRUE; }private:  T Old;};#endif // __MEMABS_HPP.D.
Anzeige der Adressen der COM-Ports HC05MM7.CPP
.D.C:\MYLIB\SAMPLE\HC05MM7.CPP,/* HC05MM7.CPP *//* * Anzeige der Adressen der COM-Ports * ================================== */#include <memabs.hpp>VOID main(VOID){  cout << endl << "Adressen der COM-Ports\n";  BIOS<UINT> COM1(rs232_port_1);  cout << "COM1:" << hex << COM1() << endl;  BIOS<UINT> COM2(rs232_port_2);  cout << "COM2:" << hex << COM2() << endl;  BIOS<UINT> COM3(rs232_port_3);  cout << "COM3:" << hex << COM3() << endl;  BIOS<UINT> COM4(rs232_port_4);  cout << "COM4:" << hex << COM4() << endl;}.D.
Operatorentest HC05MM8.CPP
.D.C:\MYLIB\SAMPLE\HC05MM8.CPP,/* HC05MM8.CPP *//* * Operatorentest * ============== */#include <memabs.hpp>VOID main(VOID){  INT i;  BIOS<UINT> a (rs232_port_4);  cout << "Using BIOS-Data-Address 40:6 "          "(=rs232_port_4)\n";  cout << "for testing Operators\n\n";  cout << "Testing operators = [abcd] and ()\n";  a = 0xabcd;  cout << hex << a() << ' ';  cout << endl;  cout << "Testing operators ++,-- starting with 3\n";  a=3;  cout << hex << a() << ' ';  for (i=0; i<4; i++)    cout << hex << ++a << ' ';  cout << hex << a() << ' ';  for (i=0; i<4; i++)    cout << hex << --a << ' ';  cout << endl;  cout << "Testing operators <<,>> starting with 1\n";  a = 1;  cout << hex << a() << ' ';  for (i=0; i<4; i++)    cout << hex << (a<<2) << ' ';  cout << hex << a() << ' ';  for (i=0; i<4; i++)    cout << hex << (a>>2) << ' ';  cout << endl;  cout << "Testing operator ~ \n";  a = 0x5aa5;  cout << hex << a() << ' ';  ~a;  cout << hex << a() << ' ';  cout << endl;  cout << "Testing operator += [1234+4321] "                       "and -= [5555-4321]\n";  a = 0x1234;  cout << hex << a() << ' ';  a += 0x4321;  cout << hex << a() << ' ';  a = 0x5555;  cout << hex << a() << ' ';  a -= 0x4321;  cout << hex << a() << ' ';  cout << endl;  cout << "Testing operator *= [10*123] "                       "and /= [1230/10]\n";  a = 0x10;  cout << hex << a() << ' ';  a *= 0x123;  cout << hex << a() << ' ';  a = 0x1230;  cout << hex << a() << ' ';  a /= 0x10;  cout << hex << a() << ' ';  cout << endl;  cout << "Testing operator %= [13%10] "                       "and &= [1234%00ff]\n";  a = 0x13;  cout << hex << a() << ' ';  a %= 0x10;  cout << hex << a() << ' ';  a = 0x1234;  cout << hex << a() << ' ';  a &= 0x00ff;  cout << hex << a() << ' ';  cout << endl;  cout << "Testing operator &= [1234|00ff] "                       "and ^= [1234^00ff]\n";  a = 0x1234;  cout << hex << a() << ' ';  a |= 0x00ff;  cout << hex << a() << ' ';  a = 0x1234;  cout << hex << a() << ' ';  a ^= 0x00ff;  cout << hex << a() << ' ';  cout << endl;  BIOS<UINT> *b = new BIOS<UINT>(rs232_port_4);  cout << "Using BIOS-Data-Address 40:6 "          "(=rs232_port_4)\n";  cout << "for testing Operators\n\n";  cout << "Testing operators = [abcd] and ()\n";  (*b) = 0xabcd;  cout << hex << (*b)() << ' ';  cout << endl;}.D.
Speicherzugriff ber pointer HC05MM9.CPP
.D.C:\MYLIB\SAMPLE\HC05MM9.CPP,/* HC05MM9.CPP *//* * Pointer-controlled memory access * ================================ */#ifndef __TINY__  #error must use TINY model#else#include <memabs.hpp>VOID main(VOID){  cout << "Pointer controlled memory access\n";  UINT *mem = new UINT[80];  cout << "at " <<hex<< mem <<endl;  cout << "contents (read by pointers):\n";  *mem = (UINT)(mem+2);  *(mem+1) = _DS;  *(mem+2) = 0xab;  cout <<hex<< mem << ':' <<hex<< *mem <<endl;  cout <<hex<< (mem+1) << ':' <<hex<< *(mem+1) <<endl;  cout <<hex<< (mem+2) << ':' <<hex<< *(mem+2) <<endl;  cout << "contents: (read by MEM-objects)\n";  MEM<UINT> off(_DS,(UINT)mem);  MEM<UINT> seg(_DS,(UINT)(mem+1));  MEM<UINT> dat(_DS,(UINT)(mem+2));  cout <<hex<< off.off() << ':' <<hex<< off() <<endl;  cout <<hex<< seg.off() << ':' <<hex<< seg() <<endl;  cout <<hex<< dat.off() << ':' <<hex<< dat() <<endl;  cout << "creating MEM_P object, reading back data\n";  MEM_P<UINT> m(_DS,(UINT)mem);  cout << "Primary-Pointer:";  cout <<hex<< m.seg() << ':'       <<hex<< m.off() <<endl;  cout << "points to      :";  cout <<hex<< m.pointseg() << ':'       <<hex<< m.pointoff() <<endl;  cout << "containing data: " <<hex<< m() <<endl;  cout << "data ++, --, &0x0f, |0xf0" <<endl;  ++m; cout << m() << ' ';  --m; cout << m() << ' ';  m&=0x0f; cout << m() << ' ';  m|=0xf0; cout << m() << ' ';  cout <<endl;  cout << "creating MEM_P object, reading back data\n";  MEM_P<UINT> *mp = new MEM_P<UINT> (_DS,(UINT)mem);  cout << "Primary-Pointer:";  cout <<hex<< mp->seg() << ':'       <<hex<< mp->off() <<endl;  cout << "points to      :";  cout <<hex<< mp->pointseg() << ':'       <<hex<< mp->pointoff() <<endl;  cout << "containing data: " <<hex<< (*mp)() <<endl;  cout << "data ++, --, &0x0f, |0xf0" <<endl;  ++(*mp); cout << (*mp)() << ' ';  --(*mp); cout << (*mp)() << ' ';  (*mp)&=0x0f; cout << (*mp)() << ' ';  (*mp)|=0xf0; cout << (*mp)() << ' ';  cout <<endl;}#endif.D.
Beginn und Ende der Tastaturwarteschlange HC05MM10.CPP
.D.C:\MYLIB\SAMPLE\HC05MM10.CPP,/* HC05MM10.CPP *//* * Beginn und Ende der Tastaturwarteschlange * ========================================= */#include <memabs.hpp>VOID main(VOID){  cout << endl;  BIOS<UINT> begin(keybd_begin);  cout << endl << "Begin of kbd-queue : ";  cout << hex << begin() << endl;  BIOS<UINT> end(keybd_end);  cout << endl << "End of kbd-queue : ";  cout << hex << end() << endl;}.D.
Teil 6: Tonerzeugung per Programm
Lautsprecher HC06LS0.C, HC06LS1.C
Am gefahrlosesten ist im PC der Lautsprecher programmierbar, da er vom BIOS und vom MSDOS nicht weiter untersttzt wird. Bevor wir den Lautsprecher tatschlich ein und ausschalten bentigen wir einige Schaltungsdetails:
           Ŀ       Ŀ                1.19MHzĿ         1 kHz   Ŀ  LS4.77 MHzĴ /4  Ĵ&    TIMER  Ĵ       Ŀ                    Ĵ     Kanal 2        &    Ĵ                                Ĵ                                      Port B  Bit 0                        8255    Bit 1          
Whrend der Timer durch das BIOS beim Booten des PC programmiert wird und auch der Grundzustand des Portbausteins 8255 eingestellt wird, kann ber die Bits 0 und 1 von Port B zu jedem spteren Zeitpunkt der am Timer vorhandene 1kHz-Ton ein- oder ausgeschaltet werden. 
Adresse Port-B: 0x61
Beispiel: 
Bit 3 in char c einschalten: c |= 0x08;
Bit 3 in char c ausschalten: c &= ~0x08;
Wir mssen beim Schalten beachten, da die Bits 2-7 von Port B andere, fr den PC lebenswichtige Funktionen haben und daher nicht verndert werden drfen. 
Falsch:  outportb(0x61,3);
Richtig: c = inportb(0x61); c |=3; outportb(0x61,c);
.D.C:\MYLIB\SAMPLE\HC06LS0.C,/* HC06LS0.C */#include <conio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  UCHAR c;  c = IN_PORT(0x61);  /* 8255, Port-B */  c |= 3;  OUT_PORT(0x61,c);  getch();  c = IN_PORT(0x61);  c &= ~3;  OUT_PORT(0x61,c);}.D.
Fragen: 
Welcher Unterschied besteht zwischen den Funktionen inportb(), outportb() und inport(), outport()?
Welche Header-Datei enthlt die Prototypen zu den Ein-Ausgabe-Funktionen? Was kennzeichnet die dort beschriebenen Prototypen? 
Damit das Programm nicht gar so kommentarlos abluft, versehen wir es mit einigen Texthinweisen mit printf() und lschen vor und nach dem Test den Bildschirm. Eine Erklrung als Kommentar zu Beginn ersparen wir durch den Text der printf-Ausgabe.
.D.C:\MYLIB\SAMPLE\HC06LS1.C,/* HC06LS1.C *//* * Ein- und Ausschalten des Lautsprechers * ====================================== */#include <dos.h>#include <conio.h>#include <stdio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  UCHAR c;  clrscr();  printf("Ein- und Ausschalten des Timers "	 "und des Lautsprechers\n\n");  printf("Taste schaltet den Lautsprecher "	 "und den Timer ein\n");  getch();  c = IN_PORT(0x61);  /* 8255, Port-B */  c |= 3;  OUT_PORT(0x61,c);  printf("Taste schaltet den Lautsprecher "	 "und den Timer aus\n");  getch();  c = IN_PORT(0x61);  c &= ~3;  OUT_PORT(0x61,c);  getch();  clrscr();}.D.
Tonerzeugung durch Softwareverzgerung, HC06LS2.C
Selbstverstndlich kann der Ton im Lautsprecher auch durch programmgesteuertes Umpolen am letzten UND-Gatter erzeugt werden. Die Frequenz ergibt sich aus der Verzgerung, die das Programm bewirkt. Es ist das keine empfehlenswerte Methode zur Erzeugung definierter Zeitabstnde, denn dazu eignet sich natrlich der Timer wesentlich besser; aber das Ergebnis gibt uns wichtige Aufschlsse ber die Arbeitsweise des Rechners. 
.D.C:\MYLIB\SAMPLE\HC06LS2.C,/* HC06LS2.C *//*  * Umpolung des Lautsprechers durch Programm * ========================================= */#include <dos.h>#include <conio.h>#include <bios.h>#include <stdio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  UCHAR c;  UINT periode;  clrscr();  printf("Erzeugen eines Tones "	 "durch Umpolung des Lautsprechers\n\n");  printf("Periodendauer durch "	 "Verzgerungsschleife verlngert\n");  printf("Start mit Taste, Abbruck mit Taste\n\n");  getch();  c = IN_PORT(0x61);     /* Timer ausschalten */  c &= ~1;  OUT_PORT(0x61,c);  for(;;)  { /* Lautsprecher umpolen */    c = IN_PORT(0x61);    c ^= 2;    OUT_PORT(0x61,c);    /* kleine Verzgerung */    for (periode=0; periode<500; periode++);    /* Abbruck mit Taste */    if (bioskey(1))    {      getch();      break;    }  }  /* Lautsprecher und Timer ausschalten */  c = IN_PORT(0x61);  c &= ~3;  OUT_PORT(0x61,c);  printf("Warum 'gurgelt' der Ton?\n");  getch();  clrscr();}.D.
Ausschalten der Interrupts, HC06LS3.C
Die Unterbrechnung des normalen Programmablaufs durch Interrupts kann auch abgeschaltet werden. Dazu knnen die C-Funktionen disable() und enable() verwendet werden. Dabei mu man allerdings beachten, da nicht nur der Timer-Interrupt, sondern auch alle anderen Interrupts, also auch der Tastatur-Interrupt abgeschaltet werden. Daher kann der Ton auch nicht durch eine Taste oder nach einer vorgegebenen Zeit abgebrochen werden, da die Tastaturabfrage mangels Interrupt abgeschaltet ist und auch die Uhr nicht weiterluft. Somit mu man eine Softwarewarteschleife einsetzen um diesen Effekt zu simulieren. 
.D.C:\MYLIB\SAMPLE\HC06LS3.C,/* HC06LS3.C *//* * Ton mit Verzgerungsschleife * ============================ */#include <dos.h>#include <conio.h>#include <stdio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  UCHAR c;  ULONG periodenzahl = 1000;  UINT      periode;  clrscr();  printf("Erzeugen eines Tones "         "durch Umpolung des Lautsprechers\n\n");  printf("Periodendauer durch "         "Verzgerungsschleife verlngert\n");  printf("Start mit Taste, "         "Konstante Anzahl von Perioden\n\n");  getch();  c = IN_PORT(0x61); /* Lautsprecher EIN */  c |= 2;  OUT_PORT(0x61,c);  c = IN_PORT(0x61); /* Timer AUS */  c &= ~1;  OUT_PORT(0x61,c);  periodenzahl = 2000; /* erster Versuch */  do  {    c = IN_PORT(0x61);    c ^= 2;    OUT_PORT(0x61,c);    for (periode=0; periode<1000; periode++) ;    periodenzahl--;  }  while (periodenzahl);  printf("Ton gurgelt noch immer, "         "Achtung, jetzt Interrupts aus!\n\n");  getch();  periodenzahl = 2000;  disable();  do  {    c = IN_PORT(0x61);    c ^= 2;    OUT_PORT(0x61,c);    for (periode=0; periode<1000; periode++) ;    periodenzahl--;  }  while (periodenzahl);  enable();  printf("Ton sollte jetzt sauber, "         "wie beim Timer gewesen sein\n");  getch();  /* Lautsprecher und Timer AUS */  c = IN_PORT(0x61);  c &= ~3;  OUT_PORT(0x61,c);  clrscr();}.D.

Die Turbo-C und Borland-C-Versionen erlauben das Einfgen von Assemblerdirektiven in den C-Code, hier asm cli und asm sti; es knnen aber auch disable() und enable() verwendet werden. 
Fragen: 
Welche Interrupts werden durch enable() und disable() ein- bzw. ausgeschaltet?
Welche Aufgabe(n) hat der Timer-Interrupt? In welcher Weise hngt er mit anderen residenten Programmen zusammen? 
Das unmittelbare Schalten von Hardwareleitungen ist sehr unvorteilhaft, es erinnert an den BASIK-PEEK-POKE. Zum einen kennt der Leser des Source-Code nur bei gut kommentierten Programmen die Aufgabe von z.B. Port 0x61. Bestenfalls ist das erste Auftreten dieser Adresse kommentiert, spter wird es weggelassen. Dazu kommt, da eine kleine Gruppe von Befehlen eine bestimmte Aufgabe ausfhrt und somit einen Funktionsnamen erhalten kann der die Aufgabe mglichst klar beschreibt. Jede Vernderung dieses Kosestcks wird nur mehr in der Funktion ausgefhrt. Die Wartung des Programms wird wesentlich vereinfacht. In unserem Fall bieten sich Funktionen zum Ein- und Ausschalten von Lautsprecher und Timer an, sowie Funktionen zum Initialisieren und Setzen des Timers. Diese neuen Funktionen werden durch Prototypen zu Beginn des Programms beschreiben und knne ab diesem Zeitpunkt verwendet werden.
Kombiton, HC06LS4.C
.D.C:\MYLIB\SAMPLE\HC06LS4.C,/* HC06LS4.C *//* * Kombinierter Ton * ================ */#include <dos.h>#include <conio.h>#include <stdio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  UCHAR c;  ULONG zyklen;  UINT  periode;  clrscr();  printf("Kombinierter Ton aus Timer+Umpolung\n\n");  printf("Periodendauer verlngert\n");  printf("Start mit Taste, "         "Konstante Anzahl von Perioden\n\n");  getch();  printf("Zuerst nur der Timer-Ton (Taste)\n");  c = IN_PORT(0x61); /* Lautsprecher und Timer EIN */  c |= 3;  OUT_PORT(0x61,c);  getch();  printf("Jetzt kombiniert mit Umpolung, "         "endet selbst\n");  zyklen = 3000;  disable();  do  {    c = IN_PORT(0x61);    c ^= 2;    OUT_PORT(0x61,c);    for (periode=0; periode<1000; periode++) ;    zyklen--;  }  while (zyklen);  enable();  printf("Jetzt wieder nur der Timer (Taste)\n");  getch();  /* Lautsprecher und Timer AUS */  c = IN_PORT(0x61);  c &= ~3;  OUT_PORT(0x61,c);  clrscr();}.D.
Timer
Eine weit bessere Mglichkeit zur Tonerzeugung stellt uns der Timer zur Verfgung. Wir mssen aber wissen, wie man den Timer programmiert. 
Timer-Demonstration, HC06TI1.C
.D.C:\MYLIB\SAMPLE\HC06TI1.C,/* HC06TI1.C *//* * Timer-Demonstration * =================== */#include <dos.h>#include <stdio.h>#include <conio.h>#include <portable.h>#include <mylib.h>#ifndef MYLIB#include "\mylib\source\spk.c"#include "\mylib\source\tim.c"#endifVOID main(VOID){  clrscr();  printf("Der Timer arbeitet vllig unabhngig "         "von gleichzeitig laufender Software\n");  printf("Taste\n");  getch();  spk_on(); tim_on();  printf("Whrend der Timer fr uns arbeitet, "         "knnen wir machen was wir wollen\n"	 "Das ist der normale Timer-Ton mit 1 kHz "         "(Taste)\n");  getch();  printf("Der Timer kann auch umprogrammiert werden!\n"	 "Steuerwort auf 0x43\n"	 "7 6 5 4 3 2 1 0 Bit\n"	 "0 0             Kanal 0 Uhr, 53 ms, IRQ0, 0x36, 0x00, 0x00 ??\n"	 "0 1             Kanal 1 DRAM-Refresh, 15 us\n"	 "1 0             Kanal 2 Lautsprecher: 0xc6, 0x33, 0x05\n"	 "    0 0         Latch\n"	 "    0 1         LSB folgt\n"	 "    1 0         MSB folgt\n"	 "    1 1         LSB-MSB folgt\n"	 "        0 0 0   Interrupt on count\n"	 "	  0 0 1   One-Shot\n"	 "	  0 1 0   Generator\n"	 "	  0 1 1   Rechteck-Generator\n"	 "	  1 0 0   Triggered-Strobe-Soft\n"	 "	  1 0 1   Triggered-Strobe-Hard\n"	 "		0 Binary\n"	 "		1 BCD\n"	 "Versuchen wir das Steuerwort 0xb6 (Taste)\n");  getch();  OUT_PORT(0x43,0xb6);  OUT_PORT(0x42,0x00); /* Zeitkonstante LSB */  OUT_PORT(0x42,0x10); /* Zeitkonstante MSB */  printf("Jetzt wieder Originalprogrammierung "         "(Taste)\n");  getch();  OUT_PORT(0x43,0xc6);  OUT_PORT(0x42,0x33); /* Zeitkonstante LSB */  OUT_PORT(0x42,0x05); /* Zeitkonstante MSB */  printf("Taste\n");  getch();  spk_off(); tim_off();}.D.
Fragen:
Die Timer-Kanle werden beim Einschalten des Rechners durch das BIOS programmiert. Die Zeitkonstante von Kanal 0 wird auf 0 eingestellt. Berechne die Periodendauer!
Welche Zahl mu in den Timer-Kanal 1 geladen werden, damit sich ein 15us-Refresh-Zyklus ergibt?
Welche Betriebsarten des Timers knnen im PC verwendet werden?
Wie sind die Ein- Ausgnge der Timer beschaltet?
Timer auslesen, HC06TI2.C
Obwohl die Timer vorteilhafterweise Interruptgesteuert arbeiten sollten, da der Zhlzeitpunkt exakt eingehalten wird, kann man auch den Zhlerstand der Timer ablesen. Dabei mu man allerdings beachten, da man nicht einen bestimmten Zhlerstand erwarten darf, wie das folgende Programm zeigt. 
.D.C:\MYLIB\SAMPLE\HC06TI2.C,/* HC06TI2.C *//* * Timer auslesen * ============== */#include <dos.h>#include <bios.h>#include <stdio.h>#include <conio.h>#include <portable.h>#include <mylib.h>#ifndef MYLIB#include "\mylib\source\tim.c"#endifVOID main(VOID){  UCHAR h;  UCHAR l;  clrscr();  printf("Der Timer kann auch ausgelesen werden\n");  printf("Timer auslesen: (Taste)\n");  getch();  tim_on();  OUT_PORT(0x43,0xB6);  OUT_PORT(0x42,0xff); /* Zeitkonstante LSB */  OUT_PORT(0x42,0xff); /* Zeitkonstante MSB */  do  {    l=IN_PORT(0x42);    h=IN_PORT(0x42);    printf("%4x ",(h<<8)+l);  }  while (bioskey(1)==0); getch();  getch();  tim_off();}.D.
Feststellung des Null-Durchgangs, HC06TI3.C
Wie stellt man auf Grund des Auslesens des Timers fest, wann der Timer 'bei 0 vorbeikommt'? Im folgendes Programm wird gezeigt, da man sich daran orientieren kann, da der neue Zhlerstand grundstzlich kleiner ist als der alte, da der Timer ein Abwrtszhler ist, nur, wenn die Zhlung wieder von vorn beginnt, also der Start-Zhlerstand eingelesen wird, ist das nicht der Fall. 
.D.C:\MYLIB\SAMPLE\HC06TI3.C,/* HC06TI3.C *//* * Timer auslesen * ============== */#include <bios.h>#include <stdio.h>#include <conio.h>#include <mylib.h>#include <portable.h>#ifndef MYLIB#include "\mylib\source\tim.c"#endifVOID main(VOID){  UCHAR h;  UCHAR l;  UCHAR h_alt;  clrscr();  printf("Timer nach Durchlauf auslesen: (taste)\n");  tim_on();  getch();  OUT_PORT(0x43,0xb6);  OUT_PORT(0x42,0xff); /* Zeitkonstante LBS */  OUT_PORT(0x42,0xff); /* Zeitkonstante MSB */  h_alt=0xffff;  do  {    l=IN_PORT(0x42);    h=IN_PORT(0x42);    if (h>h_alt)    {      printf("%4x ",(h<<8)+l);    }    h_alt = h;  }  while (bioskey(1)==0); getch();  getch();  tim_off();}.D.

Zeit messen, HC06TI4.C
.D.C:\MYLIB\SAMPLE\HC06TI4.C,/* HC06TI4.C *//* * Timer wartet * ============ */#include <bios.h>#include <stdio.h>#include <conio.h>#include <mylib.h>#ifndef MYLIB#include "\mylib\source\tim.c"#endifVOID main(VOID){  UINT count = 0;  do  {    printf ("%i ",count);    tim_wait(50);    count++;  }  while (bioskey(1)==0); getch();  clrscr();}.D.
Gleitender Ton, HC06LS5.C
Das erste Beispiel mit den etwas bersichtlicheren Funktionen ist ein Programm zur Erzeugung eines in der Frequenz ansteigenden Tones. 
.D.C:\MYLIB\SAMPLE\HC06LS5.C,/* HC06LS5.C *//* * Gleitende Tne * ============== */#include <dos.h>#include <stdio.h>#include <conio.h>#ifndef MYLIB#include "\mylib\source\spk.c"#include "\mylib\source\tim.c"#include "\mylib\source\tic.c"#endifVOID main(VOID){  UINT count_start = 0x0100;  UINT count_stop = 0x1000;  UINT count = count_start;  ULONG tic_old;  clrscr();  printf("Gleitende Tne, pro Ton 53ms \n");  printf("Taste\n");  getch();  spk_on(); tim_on(); tim_init();  printf("Das ist der normale Timer-Ton mit 1 kHz. "         "Gleitton beginnt (Taste)\n");  getch();  printf("Counter : ");  do  {    printf("%x ",count);    tim_set(count);    tic_old=tic_read();    do    {    }    while((tic_old+1)>tic_read());    count +=20;    if (kbhit()) break;  }  while(count<count_stop);  printf("\nNormaler Timer-Ton mit 1 kHz (Taste)\n");  getch();  tim_init();  printf("Ende mit (Taste)\n");  getch();  tim_off(); spk_off();  clrscr();}.D.
Rauschen, HC06LS6.C
.D.C:\MYLIB\SAMPLE\HC06LS6.C,/* HC06LS6.C *//*  * Rauschen * ======== */#include <dos.h>#include <stdio.h>#include <stdlib.h>#include <conio.h>#ifndef MYLIB#include "\mylib\source\spk.c"#include "\mylib\source\tim.c"#include "\mylib\source\tic.c"#endifVOID main(VOID){  UINT rand_start = 0x0050;  UINT rand_stop = 0x1000;  UINT count;  clrscr();  printf("Rauschen\n");  printf("Taste\n");  getch();  spk_on(); tim_on();  do  {    count = (INT)( ((LONG)rand() *		    (LONG)(rand_stop-rand_start)		   ) /		   (LONG)RAND_MAX                 ) + rand_start;    /* printf ("%x ",count); */    tim_set((UINT)count);  }  while(!kbhit());  printf("\nNormaler Timer-Ton mit 1 kHz (Taste)\n");  getch();  tim_init();  printf("Ende mit (Taste)\n");  getch();  tim_off(); spk_off();  clrscr();}.D.
Chromatische Tonleiter, HC06LS7.C
.D.C:\MYLIB\SAMPLE\HC06LS7.C,/* HC06LS7.C *//* * Tonleiter * ========= */#include <dos.h>#include <stdio.h>#include <conio.h>#ifndef MYLIB#include "\mylib\source\spk.c"#include "\mylib\source\tim.c"#endifFLOAT freq_tab[] ={  130.8,  138.6,  146.8,  155.6,  164.8,  174.6,  185.0,  196.0,  207.7,  220.0,  233.1,  246.9,  261.7,  277.2,  293.7,  311.1,  329.6,  349.2,  370.0,  392.0,  415.3,  440.0,  466.2,  493.9,  523.3,  200000.0};VOID main(VOID){  FLOAT *frequenz = &freq_tab[0];  UINT periode;  clrscr();  printf("Tonleiter, Start mit Taste\n");  getch();  spk_on(); tim_on();  do  {    periode = (UINT)(1190000.0/(*frequenz));    printf("%d ",periode);    tim_set(periode);    frequenz++;    delay(500);  }  while (*frequenz<30000.0);  printf("Ende mit (Taste)\n");  getch();  tim_off(); spk_off();  clrscr();}.D.
DUR-Tonleiter, HC06LS8.C
.D.C:\MYLIB\SAMPLE\HC06LS8.C,/* HC06LS8.C *//*  * Chromatische Tonleiter * ====================== */#include <dos.h>#include <stdio.h>#include <conio.h>#ifndef MYLIB#include "\mylib\source\spk.c"#include "\mylib\source\tim.c"#endifFLOAT freq_tab[] ={  130.8,  /*        C          0  */  138.6,  /*   Db        C#    1  */  146.8,  /*        D          2  */  155.6,  /*   Eb        D#    3  */  164.8,  /*        E          4  */  174.6,  /*        F          5  */  185.0,  /*   Gb        F#    6  */  196.0,  /*        G          7  */  207.7,  /*   Ab        G#    8  */  220.0,  /*        A          9  */  233.1,  /*   Hb        A#    10 */  246.9,  /*        H          11 */  261.7,  /*        c          0 */  277.2,  /*   db        c#    */  293.7,  /*        d          */  311.1,  /*   eb        d#    */  329.6,  /*        e          */  349.2,  /*        f          */  370.0,  /*   gb        f#    */  392.0,  /*        g          */  415.3,  /*   ab        g#    */  440.0,  /*        a          */  466.2,  /*   hb        a#    */  493.9,  /*        h          */  523.3,  /*        c1         */  0.0,  0.0};VOID main(VOID){  FLOAT *frequenz;  UINT periode;  INT tonnummer;  clrscr();  printf("Chromatische Tonleiter C1..C..c, "         "Start mit Taste\n\n");  getch();  frequenz = &freq_tab[0];  spk_on(); tim_on();  do  {    periode = (UINT)(1190000.0/(*frequenz));    printf("%d ",periode);    tim_set(periode);    frequenz++;    delay(100);  }  while (*frequenz>1.0);  printf("\n\n");  tim_off(); spk_off();  printf("\nDUR-Tonleiter C1..C..c, "         "Start mit Taste\n\n");  getch();  frequenz = &freq_tab[0];  spk_on(); tim_on();  tonnummer = 0;  do  {    periode = (UINT)(1190000.0/(*frequenz));    printf("%d ",periode);    tim_set(periode);    switch (tonnummer)    {    case 0: case 2: case 5: case 7: case 9:      frequenz++; tonnummer++;    default:      frequenz++; tonnummer++;    }    tonnummer %= 12;    delay(500);  }  while (*frequenz>1.0);  printf("\n\n");  tim_off(); spk_off();  printf("\nMOLL-Tonleiter C1..C..c, "         "Start mit Taste\n\n");  getch();  frequenz = &freq_tab[0];  spk_on(); tim_on();  tonnummer = 0;  do  {    periode = (UINT)(1190000.0/(*frequenz));    printf("%d ",periode);    tim_set(periode);    switch (tonnummer)    {    case 0: case 3: case 5: case 8: case 10:      frequenz++; tonnummer++;    default:      frequenz++; tonnummer++;    }    tonnummer %= 12;    delay(500);  }  while (*frequenz>1.0);  printf("\n\n");  tim_off(); spk_off();  getch();  clrscr();}.D.
MOLL-Tonleiter, HC06LS9.C
.D.C:\MYLIB\SAMPLE\HC06LS9.C,/* HC06LS9.C */#include <dos.h>#include <stdio.h>#include <conio.h>#ifndef MYLIB#include "\mylib\source\spk.c"#include "\mylib\source\tim.c"#endifFLOAT freq_tab[] ={  130.8,  /*        C          0  */  138.6,  /*   Db        C#    1  */  146.8,  /*        D          2  */  155.6,  /*   Eb        D#    3  */  164.8,  /*        E          4  */  174.6,  /*        F          5  */  185.0,  /*   Gb        F#    6  */  196.0,  /*        G          7  */  207.7,  /*   Ab        G#    8  */  220.0,  /*        A          9  */  233.1,  /*   Hb        A#    10 */  246.9,  /*        H          11 */  261.7,  /*        c          0 */  277.2,  /*   db        c#    */  293.7,  /*        d          */  311.1,  /*   eb        d#    */  329.6,  /*        e          */  349.2,  /*        f          */  370.0,  /*   gb        f#    */  392.0,  /*        g          */  415.3,  /*   ab        g#    */  440.0,  /*        a          */  466.2,  /*   hb        a#    */  493.9,  /*        h          */  523.3,  /*        c1         */  0.0,  0.0};VOID main(VOID){  FLOAT *frequenz;  UINT periode;  INT tonnummer;  clrscr();  printf("Chromatische Tonleiter C1..C..c, "         "Start mit Taste\n\n");  getch();  frequenz = &freq_tab[0];  spk_on(); tim_on();  do  {    periode = (UINT)(1190000.0/(*frequenz));    printf("%d ",periode);    tim_set(periode);    frequenz++;    delay(100);  }  while (*frequenz>1.0);  printf("\n\n");  tim_off(); spk_off();  printf("\nDUR-Tonleiter C1..C..c, "         "Start mit Taste\n\n");  getch();  frequenz = &freq_tab[0];  spk_on(); tim_on();  tonnummer = 0;  do  {    periode = (UINT)(1190000.0/(*frequenz));    printf("%d ",periode);    tim_set(periode);    switch (tonnummer)    {    case 0: case 2: case 5: case 7: case 9:      frequenz++; tonnummer++;    default:      frequenz++; tonnummer++;    }    tonnummer %= 12;    delay(500);  }  while (*frequenz>1.0);  printf("\n\n");  tim_off(); spk_off();  printf("\nMOLL-Tonleiter C1..C..c, "         "Start mit Taste\n\n");  getch();  frequenz = &freq_tab[0];  spk_on(); tim_on();  tonnummer = 0;  do  {    periode = (UINT)(1190000.0/(*frequenz));    printf("%d ",periode);    tim_set(periode);    switch (tonnummer)    {    case 0: case 3: case 5: case 8: case 10:      frequenz++; tonnummer++;    default:      frequenz++; tonnummer++;    }    tonnummer %= 12;    delay(500);  }  while (*frequenz>1.0);  printf("\n\n");  tim_off(); spk_off();  getch();  clrscr();}.D.
Nachdem wir in diesem Programm die wichtigsten Lautsprecher-Funktionen gebildet haben, mssen wir sie nicht immer wieder in das Programm einfgen, sondern knnen dies mit einer #include-Anweisung tun. Das hat den Vorteil, da diese Programmteile nur einmal existieren und bei Verbesserungen eines Programmstcks diese Verbesserung allen Programmen zugute kommt, die diese include-Anweisung benutzen. 
#include "ls.c"
Wenn diese Programmteile ausreichend getestet sind, kann man eine Bibliothek oft benutzter Funktionen schaffen, soda nur mehr die Prototypen mit
#include "ls.h"
eingebunden werden mssen. Die Funktionen selbst werden beim Binden aus einer Datei ls.lib geholt, genauso, wie alle anderen C-Funktionen auch. 
Fragen:
Welche Aufgabe hat eine Header-Datei? 
Welchen Nachteil haben wir bei Verwendung von Funktionen? Gibt es andere Mglichkeiten?
Teil 7: Hardware-Interrupts
Interrupts schalten, HC07IN1.C
Interrupts waren ursprnglich Hardwareereignisse, die den normalen Prozessorablauf unterbrechen. Je nach Aufbau der Prozessorhardware gibt es verschiedene Reaktionsmglichkeiten auf Interrupts. Zunchst das Polling (Serienabfrage aller in Frage kommenden Interruptquellen) oder, wie im PC, den vektorisierten Interrupt, bei dem der Prozessor durch das Hardwareereignis sofort auf die richtige Adresse, den sogenannten Interruptvektor gesteuert wird und damit Rechenzeit einspart. 
                              __Ŀ         IRQ 0 TimerCPU                         __                           IRQ 1 Tastatur           CLI              __           __              IRQ 2 frei         INTĴ    __          STI             IRQ 3 COM2                           __                          IRQ 4 COM1                           __                          IRQ 5 Drucker                           __Ĵ                   IRQ 6 Floppy                           __                          IRQ 7 Drucker                           0x21, 0=EIN, 1=AUS                                        Power good                              NMIParitt                                                   I/OCHK                                    
Das zeigen wir an einem Beispiel der Tastatur. 10 Sekunden lang gibt es keine Tastaturreaktion, whrend aber der Timer problemlos weiterluft, Interruptleitung 0 ist davon nicht betroffen. 
.D.C:\MYLIB\SAMPLE\HC07IN1.C,/* HC07IN1.C *//* * Interrupts individuell schalten * =============================== */#include <dos.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  UCHAR key;  UCHAR inte;  clrscr();  printf("Tastatur-Interrupt wird "	 "in den nchsten 10s ausgeschaltet\n");  // Tastatur-Interrupt ausschalten  inte=IN_PORT(0x21);  OUT_PORT(0x21,inte | 0x02);  delay(10000);  // Tastatur-Interrupt einschalten  inte=IN_PORT(0x21);  OUT_PORT(0x21,inte & ~0x02);  printf("Jetzt mte die Tastatur "	 "wieder funktionieren, Ende mit ESC\n");  do    key=getche();  while ((key&0xff)!=27);  clrscr();}.D.
Das funktioniert natrlich auch mit dem Timer. 
HC07IN2.C
.D.C:\MYLIB\SAMPLE\HC07IN2.C,/* HC07IN2.C *//* * Ausschalten des Timer-Interrupt * =============================== */#include <dos.h>#include <time.h>#include <bios.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  UCHAR inte;  time_t t;  clrscr();  printf("Testen des Ausschaltens "	 "des Timer-Interrupt\n");  printf("Zunchst Timer normal,\n"	 "nach jeweils 1 s kommt "	 "der nchste Zhlerstand: (Taste)\n");  do  {    time(&t);    printf ("%x ",t);    delay(1000);  }  while(bioskey(1)==0);  getch();  printf("\nWarten Sie jetzt einige Sekunden "	 "und bettigen Sie dann wieder eine Taste:\n");  getch();  printf("Sie sehen, die Zeit luft weiter.\n"	 "eine Taste dreht den Timer-Interrupt ab.\n"	 "Warten Sie wieder einige Sekunden, "	 "dann noch einmal Taste\n");  do  {    time(&t);    printf ("%x ",t);    delay(1000);  }  while(bioskey(1)==0); getch();  // Timer-Interrupt ausschalten  inte=inport(0x21);  OUT_PORT(0x21,inte | 0x01);  getch();  // Timer-Interrupt einschalten  inte=IN_PORT(0x21);  outport(0x21,inte & ~0x01);  printf("\nDie Zeit blieb stehen (Taste)\n");  do  {    time(&t);    printf ("%x ",t);    delay(1000);  }  while(bioskey(1)==0);  getch();}.D.
HC07IN3.C
Auch hier ist es ntzlich, die Schalthandlungen in zweckmigen Funktionen zusammenzufassen, wie im folgenden Beispiel gezeigt wird:
.D.C:\MYLIB\SAMPLE\HC07IN3.C,/* HC07IN3.C *//* * Tastaturinterrupt ausschalten, mit Bibliotheksfunktion * ====================================================== */#include <dos.h>#include <bios.h>#include <stdio.h>#include <conio.h>#include <mylib.h>#ifndef MYLIB#include "\mylib\source\int.c"#endifVOID main(VOID){  clrscr();  printf("Ausschalten des Tastaturinterrupt "	 "whrend der nchsten 10s.\n");  int_off(1);  delay(10000);  printf("Wiedereinschalten\n");  int_on(1);  while (bioskey(1)) bioskey(0);  getch();  clrscr();}.D.
Verndern von Interrupt-Vektoren, HC07IV1.C
.D.C:\MYLIB\SAMPLE\HC07IV1.C,/* HC07IV1.C *//* * Interruptvektor ersetzen * ======================== */#include <dos.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>VOID main(VOID){  ULONG far *ip;  ULONG alter_vektor;  clrscr();  ip=(ULONG far *)MK_FP(0,20);  disable(); alter_vektor = *ip; enable();  printf  ("Geretteter PrintScreen-Interrupt-Vektor: %lFp\n",    alter_vektor);  disable(); *ip = 0x12345678L; enable();  printf("Neuer PrintScreen-Interrupt-Vektor: %lFp\n",          *ip);  disable(); *ip = alter_vektor; enable();  printf("Wiederhergestellter alter Vektor: %lFp\n",          *ip);  getch();  clrscr();}.D.
Interrupt-Service-Routinen, HC07IV2.C
.D.C:\MYLIB\SAMPLE\HC07IV2.C,/* HC07IV2.C *//* * Interrupt-Service-Routine * ========================= */#include <dos.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>VOID interrupt far prt_scr(VOID){  UCHAR far *bild=(UCHAR far *)MK_FP(0xb800,0);  (*bild)++;}VOID main(VOID){  ULONG far *ip;  struct SREGS sr;  ULONG alter_vektor;  clrscr();  printf("Umlenkung eines Interruptvektors "         "auf das eigene Programm\n");  ip=(ULONG far *)MK_FP(0,20);  printf("Alter PrintScreen-Interrupt-Vektor: %lFp\n",         *ip);  disable(); alter_vektor = *ip; enable();  /* Umlenken des PrintScreen-Interrupt */  segread(&sr);  disable();  *ip=(ULONG)MK_FP(sr.cs,(UINT)prt_scr);  enable();  printf("Neuer PrintScreen-Interrupt-Vektor: %lFp\n",         *ip);  printf("Die neue Interrupt-Service-Routine "         "verndert den Inhalt\n"	 "der Bildschirmzelle 0,0 links oben "         "mit PRTSCR\n"	 "Jede andere Taste "         "stellt den alten Vektor wieder her\n");  do {} while (!kbhit()); getch();  disable(); *ip = alter_vektor; enable();  printf("Wiederhergestellter alter Vektor: %lFp\n",         *ip);  getch();  clrscr();}.D.
HC07IV2A.C
.D.C:\MYLIB\SAMPLE\HC07IV2A.C,/* HC07IV2A.C *//* * Interrupt-Service-Routine * ========================= */#include <dos.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>VOID interrupt prt_scr(...){  UCHAR far *bild=(UCHAR far *)MK_FP(0xb800,0);  (*bild)++;}VOID main(){  ULONG far *ip;  struct SREGS sr;  VOID interrupt (*alter_vektor)(...);  clrscr();  printf("Umlenkung eines Interruptvektors\n");  ip=(ULONG far *)MK_FP(0,20);  printf("Alter PrintScreen-Interrupt-Vektor: %lFp\n",          *ip);  alter_vektor = getvect(5);  /* Umlenken des PrintScreen-Interrupt */  segread(&sr);  setvect(5,prt_scr);  printf("Neuer PrintScreen-Interrupt-Vektor: %lFp\n",*ip);  printf("Die neue Interrupt-Service-Routine "         "verndert den Inhalt\n"	 "der Bildschirmzelle 0,0 links oben "         "mit PRTSCR\n"	 "Jede andere Taste "         "stellt den alten Vektor wieder her\n");  do {} while (!kbhit()); getch();  setvect(5,alter_vektor);  printf("Wiederhergestellter alter Vektor: %lFp\n",  *ip);  getch();  clrscr();}.D.
HC07IV3.C
.D.C:\MYLIB\SAMPLE\HC07IV3.C,/* HC07IV3.C *//* * Funktionen in der Interrupt-Service-Routine * =========================================== */#ifdef __TURBOC__#ifdef __TINY__#include <dos.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>const UINT STACKLEN=5000;static CHAR temporary_stack[STACKLEN];static UINT old_stackp;static UINT old_stacks;static UINT counter=0;VOID interrupt far prt_scr(...){  asm {    cli    // make TINY conditions cs=ds=es    mov ax,cs    mov es,ax    mov ds,ax    // remind old_stack    mov old_stackp,sp    mov ax,ss    mov old_stacks,ax    // make new_stack    mov ax,cs    mov ss,ax    mov sp,OFFSET temporary_stack    add sp,STACKLEN-1    sti      }  counter++;  printf("COUNTER:%i\n",counter);  asm {    // get old_stack    cli    mov ax,old_stackp    mov bx,old_stacks    mov ss,bx    mov sp,ax    sti      }}VOID main(VOID){  struct SREGS sr;  VOID interrupt (*alter_vektor)(...);  clrscr();  printf("Benutzung hherwertiger Funktionen "         "in der Interrupt-Service-Routine\n");  segread(&sr);  if (sr.cs!=sr.ds)  {    printf("TINY-Modell verwenden!\n");    getch();    return;  }  alter_vektor=getvect(5);  setvect(5,prt_scr);  printf("Vektor umgelenkt\n");  do {} while (!kbhit());  setvect(5,alter_vektor);  getch();  clrscr();}#else  #error Must use TINY model#endif // __TINY__#else  #error Must use BORLAND compiler#endif // __TURBOC__.D.
HC07IV3A.C
.D.C:\MYLIB\SAMPLE\HC07IV3A.C,/* HC07IV3A.C */#ifdef __ZTC__#include <dos.h>#include <int.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>INT far prt_scr(struct INT_DATA *pd){  printf(" HELLO ");  return 1;}VOID main(VOID){  printf("Benutzung hherwertiger Funktionen in der Interrupt-Service-Routine\n");  int_intercept(5,prt_scr,1000);  printf("Vektor umgelenkt\n");  do {} while (!kbhit());  int_restore(5);  getch();}#else  #error Must use ZORTECH-compiler#endif.D.
HC07IV4.C
.D.C:\MYLIB\SAMPLE\HC07IV4.C,/* HC07IV4.C *//* * Timer-Interrupt umlenken * ======================== */#include <stdio.h>#include <dos.h>#include <conio.h>#include <mytypes.h>#define INTR 0x1C    /* TIMER-Interrupt */VOID interrupt ( *oldhandler)(...);volatile INT count=0;VOID interrupt handler(...){   disable();       /* Interrupts ausschalten */   count++;         /* Zhler weiterzhlen */   enable();        /* Interrupts einschalten */   oldhandler();    /* alten Interrupt rufen */}VOID main(VOID){  /* alten Vektor merken */   oldhandler = getvect(INTR);   /* neuen Vektor installieren */   setvect(INTR, handler);   /* zhlen und warten */   while (count < 20) printf("%d \n",count);   /* alten Vektor wiederherstellen */   setvect(INTR, oldhandler);}.D.
HC07IV5.C
.D.C:\MYLIB\SAMPLE\HC07IV5.C,/* HC07IV5.C *//* * Tastatur-Interrupt umlenken * =========================== */#include <stdio.h>#include <dos.h>#include <conio.h>#include <mytypes.h>#define INTR 0x9    /* Tastatur-Interrupt */VOID interrupt ( *oldhandler)(...);VOID interrupt handler(...){   UINT far * kp;   oldhandler();    /* alten Interrupt rufen */   disable();       /* Interrupts ausschalten */   kp = (UINT far *)MK_FP(0x0040,0x001a);   kp = (UINT far *)MK_FP(0x0040,*kp);   if ((*kp & 0x0060) == 0x0060)     *kp = (*kp) & ~0x0020;   else if ((*kp & 0x0040) == 0x0040)     *kp = (*kp) | 0x0020;   enable();        /* Interrupts einschalten */}VOID main(VOID){   UINT c;   /* alten Vektor merken */   oldhandler = getvect(INTR);   /* neuen Vektor installieren */   setvect(INTR, handler);   printf("\n");   do   {     c=getch();     printf("%c",c);   }   while ((c)!=3);   /* alten Vektor wiederherstellen */   setvect(INTR, oldhandler);}.D.
Der Timer luft schneller, HC07IT0.C
Die normale Periode zur Unterbrechung eines Vordergrundprogramms ist 53ms. Fr normale Anwendungen ist dieser Wert klein genug, um unbemerkt eine Hintergrund-Aufgabe oder eine quasi-gleichzeitig stattfindende auszufhren. Anderseits ist sie auch gro genug, um ein Vordergrundprogramm nicht unntig oft zu verzgern. 
Es gibt aber Anwendungen, z.B. bei der bertragung ber die parallele Schnittstelle in festen Zeitabstnden oder bei Spielen oder bei abtastenden Messungen, wo dieser zeitiche Abstand einfach zu gro ist, um beim Benutzer akzeptables Verhalten zu erreichen. Immer, wenn wir ein schnelleres Zeitma bentigen, ist es erforderlich, den Timer-Kanal-0 umzuprogrammieren.
Der Haken dabei ist, da jede Beschleunigung des Timers auch eine Beschleunigung der internen Uhr zur Folge hat, da die interne Uhr vom Timer-Kanal-0 abgeleitet wird. Um hier fehlerfreie Programme zu erreichen gibt es zwei Methoden: 
a. die brutale: Die interne Uhr ist uns gleichgltig, nach Beendigung unseres Programms holen wir die Zeit aus dem CMOS-RAM und setzen die Uhrzeit neu. 
b. die sanfte: Wir bercksichtigen die schneller laufende Uhr und rufen die Timer-Interrupt-Routine entsprechend seltener auf. 
Es ist klar, da die 'brutale' Methode nur bedingt funktioniert. Was etwa, wenn das Programm Dateien speichert, dann bekommen diese alle eine falsche Uhrzeit verpat! Bei der Interrupt-Programmierung mu man sich aber bewut sein, da man nicht allein ist auf der Welt und im PC, sondern, da sich viele andere Programme auch an den Timer-Interrupt anhngen und wir erstaunt sein werden, was dann alles nicht mehr funktioniert. 
Knnen wir diesen beschleunigten Timer beliebig schnell machen? Wo sind die Grenzen?
Betrachten wir die 'sanfte' Methode, dann knnen wir noch zwei Variationen unterscheiden: 
1. die 'brutal-sanfte': Wir erhalten einen beschleunigten Timer-Interrupt, kmmern uns aber nicht um etwaige andere Hintergrundaufgabe, sondern zhlen in Eigenregie nur die Zeit im Hauptspeicher hoch, soda der Fehler der 'brutalen' Methode nicht aufscheint, dafr knnen wir vermutlich die Beschleunigung sehr hoch treiben, da niemand anders unsere Timer-Routine ungewollt verlngert. 
2. die 'wirklich sanfte': Wir lassen alle 53 ms die alte Interrupt-Routine 'zu Wort kommen'. Das ist zwar vornehm, aber bedeutet fr uns eine Einschrnkung: Die Zeit, die die alte Interruptroutine gemeinsam mit unserer neuen Aufgabe braucht beschrnkt die Mglichkeit zur Beschleunigung des Timers. 
Zuerst wollen wir ganz sorgsam verfahren und demonstrativ zeigen, wie sich die Timer-Beschleunigung auswirkt und wo ihre Grenze ist. Die Beschleunigung geht so:
Der Anfangszhlerstand (nach dem Booten) des Timers wird, ausgehend vom Anfangswert 0 (=10000h) immer halbiert, und dabei gleichzeitig eine globale Variable divisor, ausgehend vom Wert 1 verdoppelt. Der neue Timer-Interrupt-Handler, zhlt eine Variable count solange hoch, bis divisor erreicht ist, dann aktiviert er den alten Timer-Interrupt. 
Wichtige Voraussetzungen: Es darf nicht der Benutzer-Timer-Interrupt 1ch, sondern es mu der Timer-Interrupt 8h verwendet werden, da im Falle von 1ch die eigentliche Timeraufgabe, das Hochzhlen der Zeit, bereits ausgefhrt worden wre. Da wir aber jetzt die ersten sind, die den Timer-Interrupt bearbeiten (und nur im Ausnahmefall die alte Routine aufrufen) sind wir auch dafr verantwortlich, den sogenannten EOI-Kode (0x20) an den Interrupt-Kontroller zu schicken, der zufllig die Adresse 0x20 hat. Dann aber gehts!
Als Indikator fr die Beschleunigung verwenden wir der Einfachheit halber, wie so oft, den eingebauten Lautsprecher, sie knnen aber auch einen zustzlichen Lautsprecher an die Datenleitung D0 des Druckerports LPT1 anschlieen (Achtung: Vorwiderstand, ca. 200 Ohm nicht vergessen!). 
.D.C:\MYLIB\SAMPLE\HC07IT0.C,/* HC07IT0.C *//* * Variable Frequenzen durch Timer-Interrupt * ========================================= * nicht den Benutzer-Timer-Interrupt 1Ch, * sondern 08h benutzen! */#include <stdio.h>#include <conio.h>#include <bios.h>#include <dos.h>#include <mylib.h>#ifndef MYLIB#include "\mylib\source\tim.c"#endif#define INTR 0x08    /* TIMER-Interrupt */VOID interrupt ( *oldhandler)(...);VOID frequency(LONG c, UINT div);INT divisor = 1;volatile count=0;VOID interrupt handler(...){   spk_toggle();   par_toggle();   if (++count==divisor)   {     count=0;     oldhandler();    /* alten Interrupt rufen */   }   else   {     outportb(0x20,0x20);   }   enable();}VOID main(VOID){   oldhandler = getvect(INTR); /* alten Vektor merken */   clrscr();   printf("Timerintervall von 53ms "          "schrittweise verkleinern\n");   printf("Kontrolle mit Lautsprecher, "          "weiter jeweils mit Taste\n");   printf("!! Nicht mit CTRL-BREAK unterbrechen !!\n");   frequency(0x10000L,1);   frequency(0x8000,2);   frequency(0x4000,4);   frequency(0x2000,8);   frequency(0x1000,16);   frequency(0x0800,32);   frequency(0x0400,64);   frequency(0x0200,128);   frequency(0x0100,256);   frequency(0x0080,512);   disable();   setvect(INTR, oldhandler);   enable(); /* alten Vektor wiederherstellen */}VOID frequency(LONG c, UINT div){  FLOAT f;  LONG cnt;  cnt=c; f=4770000.0/(4*cnt);  printf("\nFrequenz: %6.2f Hz "         "Periodendauer: %6.2f us\n",f,1000000L/f);  disable(); divisor=div; tim_set((INT)cnt); enable();  do { tic_show(); } while (bioskey(1)==0); bioskey(0);}.D.
Fragen: Bei welcher Timer-Beschleunigung wird bei Ihrem Rechner der Ton unsauber? Nie? Gratulation, Sie haben 'ein schnelles Eisen'! Was mte man tun, um beispielsweise eine Verfnffachung (und nicht eine Vervierfachung) zu erreichen? Schreiben Sie eine allgemeine Funktion, die unsere Aufgabe fr eine beliebig whlbare Zeit erfllt und testen Sie diese! 
Anmerkung: Der Autor hatte beim Testen dieses Programms Schwierigkeiten mit den Gleitkommaeinstellungen von BORLANDC-2.0. Es ging nur mit 'Fast-floating-point OFF' und keine Emulation sondern nur mit '80287'. Andere Einstellungen fhrten zu nicht nher erkrten Speicherfehler und Abstrzen, ohne auch nur die erste Zeile auszugeben, in der eine Ausgabe einer Gleitkommazahl gewnscht war. Erklrungen erwnscht! 
Wie man dieses Prinzip nutzbringend anweden kann? Nehmen wir an wir htten irgendeine Programmieraufgabe und wollten im Hintergrund etwas gleichzeitig erledigen. Die Programmieraufgabe ist einfach: die Eingabe eines Textes; die Hintergrundaufgabe ist das Bewegen einer Figur am Bildschirm und die Ausgabe eines Tones. Innerhalb des Textausgabeprogramms knnen die Hintergrundprogramme ein- und ausgeschaltet werden sowie ihre Geschwindigkeit verndert werden: 
Hintergrundprogrammierung, HC07IT1A.C, HC07IT1.C
Programme, die wesentliche Interrupts, wie den Timer-Interrupt einer ist, verndern, sind extrem absturzgefhrdet, da am Timer0 die Existenz des ganzen Rechners aufbaut. Es ist daher ratsam, die Hintergrundaufgabe, die fr das Interruptprogramm nur eine kurzer 'Durchlaufer' ist, vorher als Hauptprogramm zu testen. In unserem Fall ist das Ein- und Ausschalten des Lautsprechers schon getestet worden, es bleibt nur die Bedienung des Bildschirms: 
Das Programm malt ein Zeichen auf den Bildschirm, hier das Zeichen 0x02 ('Gesicht') und bewegt es im Winkel von 45 Grad solange, bis es an einem der Rnder ankommt. Dann wird das Gesicht reflektiert, die Bewegungsrichtung kehrt um. 
.D.C:\MYLIB\SAMPLE\HC07IT1A.C,/* HC07IT1A.C *//* * Zeichen wandert ber den Bildschirm * =================================== */#include <stdio.h>#include <conio.h>#include <bios.h>#include <dos.h>#include <mytypes.h>volatile INT fun = 1;volatile INT x=78, y=7;volatile INT dx=1, dy=1;UINT fun_old_char;INT fun_start=1;UINT far *fp;VOID main(VOID){   clrscr();begin:   if (fun)   {     if (fun_start)     {       fp = (UINT far *) MK_FP(0xb800,(y*80+x)*2);       fun_old_char=*fp;       fun_start=0;     }     *fp=fun_old_char;     x += dx; y += dy;     fp = (UINT far *) MK_FP(0xb800,(y*80+x)*2);     fun_old_char=*fp; *fp=0x8702;     if ((x==79)||(x==0)) { dx *= -1; }     if ((y==24)||(y==0)) { dy *= -1; }   }   else   {     if (!fun_start)     {       *fp=fun_old_char;       fun_start=1;       return;     }   }   delay(100);   if (kbhit())   {     fun=0;   }   goto begin;}.D.
Statt der Verzgerung durch den Timer finden Sie hier: delay(100). Die Variable fun schaltet die Bildschirmbewegung ein und aus. Beachten Sie, da das Programm nur auf CGA,EGA oder VGA, nicht aber auf MDA oder HGC luft; in diesen Fllen wre die Segmentadresse bes Bildanfangs von 0xb800 auf 0xb000 zu ndern. Die Variablen x und y sind die augenblicklichen Koordinaten des Bewegungsablaufes, dx und dy sind die Bewegungszu- bzw. -abnahme. Es beginnt mit einer Bewegung in positive x und y-Richtung mit dx=1 und dy=1. Die Umkehr der Bewegungsrichtung erfolgt, wenn die Figur an den Bildschirmrndern anlangt, dx und dy werden dann -1. Damit unser Bildschirmvirus das alte Bild wiederherstellen kann, es ist ein sympatischer Virus, merkt sich die Interrupt-Routine in der Variablen fun_old_char den gerade berschriebenen Wert. Die Variable fun_start ist zu Beginn auf 1 gesetzt und verhindert, da der Beginnpunkt mit einem falschen Zeichen berschrieben wird, sie wird gleich nach dem ersten Durchlauf auf 0 gesetzt. Ebenso ermglich sie es, da, nach dem Beendigen des Hintergrundprogramms, die letzte berschriebene Bildschirmstelle widerhergestellt wird und nicht etwa die letzte Virusposition bestehen bleibt. Schlilich haben wir einen Zeiger fp auf den aktuellen Bildschirmpunkt. Die Variablen, die durch den Interrupt-Handler beeinflut werden sind volatile, das signalisiert dem Compiler, da sich ihr Wert immer ndern kann. Alle globalen Variablen sind static, d.h. ihr Name wird keinem weiteren Modul bekanntgegeben: eine einfache Mglichkeit in C Kollisionen von Variablennamen und zufllige Aufrufe in anderen Moduln zu verhindern. 
Das Programm selbst ist einfach. An den Rndern erfolgt eine Umkehr der Bewegungsrichtung. Die Taste beendet das Programm nicht sofort, sondern setzt zuerst die Hintergrundprogramme in den Aus-Zustand, wartet mit delay(100) noch einen Timer-Interrupt ab, damit die beschriebene Lschung des Virus mglich wird und beendet das Programm. 
Erst wenn dieses Programm ausreichend getestet wurde knnen wir daran gehen, es auch in die Timer-Interrupt-Service-Routine einzubauen: 
.D.C:\MYLIB\SAMPLE\HC07IT1.C,/* HC07IT1.C *//* * Texteingabe mit Hintergrundprogramm * =================================== */#include <stdio.h>#include <conio.h>#include <bios.h>#include <dos.h>#include <mylib.h>#ifndef MYLIB#include "\mylib\source\tim.c"#include "\mylib\source\spk.c"#endifVOID tim0_set(VOID);#define INTR 0x08    /* TIMER-Interrupt */VOID interrupt ( *oldhandler)(...);INT divi = 1;volatile count=0;volatile INT spk = 1;volatile INT fun = 1;volatile INT x=78, y=7;volatile INT dx=1, dy=1;UINT fun_old_char;INT fun_start=1;UINT far *fp;VOID interrupt handler(...){   if (spk)   {     spk_toggle();     par_toggle();   }   if (fun)   {     if (fun_start)     {       fp = (UINT far *) MK_FP(0xb800,(y*80+x)*2);       fun_old_char=*fp;       fun_start=0;     }     *fp=fun_old_char;     x += dx; y += dy;     fp = (UINT far *) MK_FP(0xb800,(y*80+x)*2);     fun_old_char=*fp; *fp=0x8702;     if ((x==79)||(x==0)) { dx *= -1; }     if ((y==24)||(y==0)) { dy *= -1; }   }   else   {     if (!fun_start)     {       *fp=fun_old_char;       fun_start=1;     }   }   if (++count==divi)   {     count=0;     oldhandler();    /* alten Interrupt rufen */   }   else   {     outportb(0x20,0x20);   }   enable();}VOID main(VOID){  /* alten Vektor merken */   oldhandler = getvect(INTR);   clrscr();   printf("Texteingabe mit Hintergrundprogramm\n"	  "PgUp Schaltet den Lautsprecher ein\n"	  "PgDn Schaltet den Lautsprecher aus\n"	  "CuUp Schaltet das Mnnchen ein\n"	  "CuDn Schaltet das Mnnchen aus\n");   printf("CuLt verzgert\n",	  "CuRt beschleunigt\n",	  "ESC  beendet\n");   fflush(stdout);   /* neuen Vektor installieren */   disable(); divi=1; setvect(INTR,handler); enable();   for (;;)   {     UINT key;     key=bioskey(0);     switch(key)     {     case 0x4900 /* PgUp */:       spk = 1; break;     case 0x5100 /* PgDn */:       spk = 0; break;     case 0x4800 /* CuUp */:       fun = 1; break;     case 0x5000 /* CuUp */:       fun = 0; break;     case 0x4b00 /* CuLt */:       divi=(divi==1)?1:divi/2;       tim0_set(); break;     case 0x4d00 /* CuRt */:       divi=(divi==16)?16:divi*2;       tim0_set(); break;     case 0x011b /* ESC  */: goto end;     }     if (key & 0x00ff) /* ASCII-Key */       putchar(key&0x00ff);   }end:   fun=0; spk=0; delay(100);   divi=1; tim0_set();   /* alten Vektor wiederherstellen */   disable(); setvect(INTR,oldhandler); enable();}VOID tim0_set(VOID){  ULONG count;  count=0x10000L;  if (divi>1) count /= divi;  disable(); tim0_set((INT)count); enable();}.D.
Testen Sie selbst! Es geht ganz schn schnell, unser Hintergrundprogramm. Es ist also durchaus mglich einiges hineinzupacken. Versuchen Sie vielleicht als bung mehrere Ping-Pong-Blle dieser Art oder zufllig erscheinende und nach einem Zufallsproze wider verschwindende Bildschirmviren zu erzeugen. Etwas spter werden wir versuchen diese Programme resident abzulegen, soda sie sich nicht nur bei unserm Trivialprogramm, sondern berall bemerkbar machen knnen. 
Teil 8: Software-Interrupts
BIOS-Interrupts, HC08IB0.C
.D.C:\MYLIB\SAMPLE\HC08IB0.C,/* HC08IB0.C *//* * Testen von BIOS-Interrupts * ========================== */#include <stdio.h>#include <stdlib.h>#include <conio.h>#include <dos.h>#include <mytypes.h>VOID main(VOID){  union REGS r;  CHAR temp[20];  INT lfw;  printf("Testen einiger BIOS-Interrupts\n");  printf("Speicherkapazitt feststellen, "         "Interrupt 0x12\n");  int86(0x12,&r,&r);  printf("Der Speicher ist %u kByte gro\n", r.x.ax);  printf("Ausstattung feststellen, Interrupt 0x11\n");  int86(0x11,&r,&r);  printf    ("Bitkombination gelesen : %16x HEX\n",r.x.ax);  printf    ("Bitkombination gelesen : %16s BIN\n",    itoa(r.x.ax,temp,2));  printf    ("Bitkombination         : ddsgsssDffvvrr7f\n");  printf    ("Laufwerk               : %i\n",    r.x.ax & 0x0001);  printf    ("Arithmetik-Prozessor   : %s\n",    (r.x.ax & 0x0002) ? "nein" : "ja");  printf    ("RAM auf Hauptplatine   : %i x16k\n",    (r.x.ax & 0x000C)>>2 );  printf("Bildschirmmodus        : ");  switch ((r.x.ax&0x0030)>>4)  {  case 0: printf("unklar\n"); break;  case 1: printf("40x25 Farbe\n"); break;  case 2: printf("80x25 Farbe\n"); break;  case 3: printf("80x25 s/w\n"); break;  }  printf    ("Anzahl der Laufwerke   : %i\n",    (r.x.ax & 0x00C0)>>6);  printf    ("DMA mglich            : %s\n",    (r.x.ax & 0x0100) ? "nein" : "ja");  printf    ("Anzahl der RS232-Karten: %i\n",    (r.x.ax & 0x0E00)>>9);  printf    ("Game-Port vorhanden    : %s\n",    (r.x.ax & 0x1000) ? "nein" : "ja");  printf    ("Serieller Drucker      : %s\n",    (r.x.ax & 0x2000) ? "nein" : "ja");  printf    ("Druckeranzahl          : %i\n\n",    (r.x.ax & 0xC000)>>12);  printf("Laufwerke feststellen\n");  for (lfw=0; lfw<5; lfw++)  {    r.h.ah=0x15;    if (lfw<2) r.h.dl=lfw;    else r.h.dl=lfw+0x80-2;    int86(0x13,&r,&r);    printf("Laufwerk %i: Laufwerkstyp :",lfw);    switch(r.h.ah)    {    case 0:      printf("kein Laufwerk\n");      break;    case 1:      printf("Floppy ohne Diskettenwechselsignal\n");      break;    case 2:      printf("Floppy mit Diskettenwechselsignal\n");      break;    case 3:      printf("Festplatte\n");      break;    }    if (r.h.ah==3) /* Festplatte */      printf("Anzahl der 512-byte-Sektoren: %16lu\n",             r.x.cx*65536L+r.x.dx);  }  printf("\n");  printf("Aktuelle Laufwerksparameter\n");  for (lfw=0; lfw<5; lfw++)  {    r.h.ah=0x08;    if (lfw<2) r.h.dl=lfw;    else r.h.dl=lfw+0x80-2;    int86(0x13,&r,&r);    printf      ("Laufwerk: "       "Status(HEX)/Laufwerke/Seitenzahl/"       "Sektoren/Spuren\n");    printf      ("%d:%x/%d/%d/%d/%d\n",       lfw,r.h.ah,r.h.dl,r.h.dh,r.h.cl,r.h.ch);  }  printf("\n");  r.h.ah = 0x0f;  int86(0x10,&r,&r);  printf("Bildschirmmodus : %x HEX\n",r.h.al);  printf("Bildschirmseite : %d\n",r.h.bh);  printf("Zeichen/Zeile   : %d\n",r.h.ah);  printf("Mit TASTE lassen wir einen Teil "         "des Bildschirms nach oben rollen\n");  printf("und verndern dabei das Attribut\n");  getch();  r.h.ah=0x06;  r.h.al=4;  r.h.bh = 0x71;  r.h.ch = 5;  r.h.cl = 10;  r.h.dh = 15;  r.h.dl = 40;  int86(0x10,&r,&r);  getch();  printf("\nUhr ablesen und Tastatur prfen\n");  do  {    r.h.ah=0x02;    int86(0x1a,&r,&r);    printf("%x:%x:%x ",r.h.ch,r.h.cl,r.h.dh);    delay(1000);    r.h.ah=1;    int86(0x16,&r,&r);  }  while(r.x.flags & 0x0040);}.D.

Bei diesem Programm ist, wie bei vielen hardwarenahen Programmen, die Position einzelner Flag-Bits entscheidend. Im 8086 sind die Zustandsbits (Flags) in zwei 8-Bytes zusammengefat. Die Bedeutung der Bits im einzelnen ist:
Ŀ1111ODITSZ?A?P?C         | | | | | Zero|   |   |         | | | | Sign  |   |   |         | | | Trap    |   |   Carry         | | Interrupt |   Parity         | Direction   |         Overflow      Auxillary Carry
MSDOS-Interrupts, HC08ID1.C
.D.C:\MYLIB\SAMPLE\HC08ID1.C,/* HC08ID1.C *//* * Demonstration des INT-21h * ========================= */#include <dos.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <mytypes.h>#include <portable.h>INT main(VOID){  union REGS r;  struct SREGS s;  UCHAR sector[512];  INT i;  CHAR dateiname[80]="TEST.TXT";  UINT dateinummer;  CHAR testdaten[80]="TESTDATEN\n";  UCHAR far * cp;  UINT far * up;  UCHAR len;  printf("MSDOS ist in der Hauptsache ein Platten- und "	 "Speicherverwaltungsprogramm\n");  printf("Holen wir die wichtigsten Diskettendaten "         "aus dem URLADER:\n");  do  {    r.h.ah=0x02;    r.h.al=1;    r.h.ch=0;    r.h.cl=1;    r.h.dh=0;    r.h.dl=0;    segread(&s);    s.es=s.ds;    r.x.bx=(UINT)&sector[0];    int86x(0x13,&r,&r,&s);    if (r.h.ah)    {      printf("Fehlercode: %x\n",r.h.ah);      if (r.h.ah==6) printf("Diskette entfernt\n");    }  }  while (r.h.ah);  {    printf("Systembezeichnung         :");    for (i=3; i<12; i++)      printf("%c",sector[i]); printf("\n");    printf("Bytes/Sektor              :%i\n",           sector[11]+sector[12]*256);    printf("Sektoren/Cluster          :%i\n",           sector[13]);    printf("Reservierte Anfangssekt.  :%i\n",           sector[14]+sector[15]*256);    printf("Anzahl der FAT-Kopien     :%i\n",           sector[16]);    printf("Stammverzeichniseintrge  :%i\n",           sector[17]+sector[18]*256);    printf("Sektoren/Disk             :%i\n",           sector[19]+sector[20]*256);    printf("Formatkennzeichen         :%2x\n",           sector[21]);    printf("ff:D-8,fe:S-8,fd:d9,fc:s9\n");    printf("Sektoren/FAT              :%i\n",           sector[22]+sector[23]*256);    printf("Sektoren/Spur             :%i\n",           sector[24]+sector[25]*256);    printf("Kopfzahl                  :%i\n",           sector[26]+sector[27]*256);    printf("Reservierte Sektoren      :%i\n",           sector[28]+sector[29]*256);  }  printf("Jetzt stellen wir fest, "         "was auf dem Laufwerk noch frei ist:\n");  r.h.ah=0x36;  r.h.dl=0; /* Laufwerk A */  intdos(&r,&r);  printf("Sektoren/Cluster  : %i\n",r.x.ax);  printf("Verfgbare Cluster: %i\n",r.x.bx);  printf("Bytes/Sektor      : %i\n",r.x.cx);  printf("Cluster Gesamt    : %i\n",r.x.dx);  printf("\nJetzt legen wir auf dem Laufwerk A "         "eine Datei an\n");  r.h.ah=0x3c;  r.x.cx=0;  r.x.dx=(UINT)dateiname;  segread(&s);  intdosx(&r,&r,&s);  if (r.x.cflag)  {    printf("Fehler: %x\n",r.x.ax);    return EXIT_FAILURE;  }  dateinummer=r.x.ax;  printf("Dateinummer: %i\n",dateinummer);  printf("Daten schreiben %s\n",testdaten);  r.h.ah=0x40;  r.x.bx=dateinummer;  r.x.cx=strlen(testdaten);  r.x.dx=(UINT)testdaten;  segread(&s);  intdosx(&r,&r,&s);  if (r.x.cflag)  {    printf("Fehler: %x\n",r.x.ax);    return EXIT_FAILURE;  }  printf("Anzahl der geschriebenen Datenbytes: %x\n",          r.x.ax);  printf("Datei schlieen\n");  r.h.ah=0x3e;  r.x.bx=dateinummer;  intdos(&r,&r);  if (r.x.cflag)  {    printf("Fehlercode : %x\n",r.x.ax);    return EXIT_FAILURE;  }  printf("Hurra\n");  printf("\nJetzt lesen wir aus dieser Datei\n");  r.h.ah=0x3d;  r.h.al=0; /* Lesen */  r.x.dx=(UINT)dateiname;  segread(&s);  intdosx(&r,&r,&s);  if (r.x.cflag)  {    printf("Fehler: %x\n",r.x.ax);    return EXIT_FAILURE;  }  dateinummer=r.x.ax;  printf("Dateinummer: %i\n",dateinummer);  printf("Daten lesen\n");  r.h.ah=0x3f;  r.x.bx=dateinummer;  r.x.cx=100;  r.x.dx=(UINT)testdaten;  segread(&s);  intdosx(&r,&r,&s);  if (r.x.cflag)  {    printf("Fehler: %x\n",r.x.ax);    return EXIT_FAILURE;  }  printf("Anzahl der gelesenen Datenbytes: %x: %s",         r.x.ax,testdaten);  printf("Datei schlieen\n");  r.h.ah=0x3e;  r.x.bx=dateinummer;  intdos(&r,&r);  if (r.x.cflag)  {    printf("Fehlercode : %x\n",r.x.ax);    return EXIT_FAILURE;  }  printf("Hurra\n");  printf("Was kann man ber "         "das aktuell ausgefhrte Programm sagen?\n");  r.h.ah=0x62;  intdos(&r,&r);  segread(&s);  printf("Das aktuelle Datensegment ist: %x\n",s.ds);  printf("Das aktuelle Codesegment ist : %x\n",s.cs);  printf("Das PSP-Segment ist          : %x\n",r.x.bx);  printf("Die wichtigsten PSP-Informationen:\n");  up=(UINT far *)MK_FP(r.x.bx,0x02);  printf("Speicherende bei : %x\n",*up);  cp=(UCHAR far *)MK_FP(r.x.bx,0x80);  len = *cp;  printf("Parameterlnge: %x\n",*cp);  printf("Parameter: ");  for (i=0; i<len; i++)  {    cp++;    printf("%c",*cp);  }  up = (UINT far *)MK_FP(r.x.bx,0x2c);  printf("\nSegmentadresse der Umgebung: %x\n",*up);  printf("Umgebungsvariable:\n");  cp = (UCHAR far *)MK_FP(*up,0);  do  {    if (*cp)      printf("%c",*cp);    else      printf(" ");    cp++;  }  while (*cp | *(cp+1));  printf("\n");  return EXIT_SUCCESS;}.D.
Teil 9: DOS-Speicherverwaltung
Wo steht was im Speicher, HC09DM1.C
.D.C:\MYLIB\SAMPLE\HC09DM1.C,/* HC09DM1.C *//* * DOS: Wo steht was im Speicher * ============================= *//*Literaturhinweise:   Scharl,Schchle,PROLOAD,PC-NEWS 2/89, S.29-31  Tom Hogan,    Die PC-Referenz fr Programmierer,    Microsoft-Press, 1988, S 162-166*/#include <dos.h>#include <stdio.h>#include <mytypes.h>#include <portable.h>VOID main(VOID){  union REGS r;  struct SREGS s;  UCHAR far * mcb_kennung;  UINT far * list_beg;  UINT far * psp;  UINT far * length;  INT block_nr=0;    r.h.ah=0x52;  intdosx(&r,&r,&s);  list_beg = (UINT far *)MK_FP(s.es,r.x.bx-2);  mcb_kennung = (UCHAR far *)MK_FP(*list_beg,0);  while ((*mcb_kennung=='M') || (*mcb_kennung=='Z'))  {    psp = (UINT far *)(mcb_kennung+1);    length = (UINT far *)(mcb_kennung+3);    printf("\n%2i:%4x %4x %4x ",      block_nr,(UINT)FP_SEG(mcb_kennung),*psp,*length);    { /* Memory-Control-Block */      INT i;      UCHAR far * c = mcb_kennung+5;      for (i=5; i<16; i++)      {        printf("%2x ",*c);        c++;      }      printf("\n                  ");      c = mcb_kennung+5;      for (i=5; i<16; i++)      {        if ((*c<32) || (*c>127))          printf("%2c ",'.');        else          printf("%2c ",*c);        c++;      }      printf("\n");    }    { /* PSP */      UCHAR far * environ;      UINT count,i;      psp = (UINT far *)MK_FP(*psp,0);      if (*psp==0x20cd) /* INT 20H, Termination-Code */      {        printf("Standard PSP-Format at %4x\n",          FP_SEG(psp));        { /* PrINT Environment */	  UINT far * environ_adr =            (UINT far *)MK_FP(FP_SEG(psp),0x2c);	  environ = (UCHAR far *)MK_FP(*environ_adr,0);          printf("Environment at: %lp\n",environ);          while (!(*environ==0 && *(environ+1)==0))          {            printf("%c",*environ);            environ++;          }          printf("\n");          count = *(environ+2);          environ += 4;          printf("Actual process: ");          for (i=0; i<count; i++)          {            while (*environ)            {              printf("%c",*environ);              environ++;            }            printf(" ");            environ++;          }          printf("\n");	}      }      else        printf("Unknown PSP-Format at %4x\n",FP_SEG(psp));    }    if (*mcb_kennung=='Z') break;    mcb_kennung =      (UCHAR far *)MK_FP(FP_SEG(mcb_kennung) +      (*length) + 1, 0);    block_nr++;  }}.D.
Maussteuerung
Parallele Schnittstelle
Schnittstellensignale
Grundstzlich kann anstelle eines Druckers auch ein beliebig anderes Datenendgert, das eine Centronics- Schnittstelle besitzt, angeschlossen werden. Alle Signale der Schnittstelle haben TTL-Pegel. 
Strobe (STB):Rechnersignal zur Dateneingabe an den Drucker. Normalerweise auf H-Pegel, wobei die Daten eingelesen werden wenn STB auf L-Pegel geht. (min 0.5s L). 
Data (D0 - D7):8 Datenleitungen. Das Einlesen der Signale wird vom STB Impuls synchronisiert. Ein H-Pegel bedeutet "1". Das Signal mu min. 0.5s anliegen, bevor der STB Impuls vom Rechner auf den Drucker als Zeichen der bernahme gegeben wird.
Busy:Das Signal liegt auf H wenn der Drucker arbeitet. Das Signal ist H: 
*) Der Drucker verarbeitet Daten*) Der Druckerpuffer ist voll*) Der Drucker ist OFF LINE*) Fehler
Acknowledge (ACK):Es wird gesendet, wenn das BUSY-Signal von H auf L wechselt. Es handelt sich somit um ein Daten-Anforderungssignal. Wird der Drucker ON LINE geschaltet sendet er autom. das ACK-Signal. (Im Ruhe- zustand H)
Papier Ende (PE):Das Signal liegt normalerweise auf L-Pegel und wechselt auf H-Pegel, wenn der Zustand PAPIER ENDE auftritt.
RESET/ auch INIT oder PRIME:Dieses vom Rechner kommende Signal initialisiert den Drucker. (Initialisierung: H auf L) Es kann jederzeit empfangen werden.
ERROR:Geht bei FEHLER auf L Pegel: *) PAPIER ENDE*) OFF LINE*) Durch eine berlast
Weiters sind manchmal auch die Signale:SELECT (Betriebsbereitschaft)AUTO FEED (ob nach jedem CR ein LF folgen soll)Signal-Masse (Masse) +5V (max. 10 - 50mA)
Eine Besonderheit der Datenleitungen ist die Mglichkeit ausgesendete Daten wieder zurckzulesen. (z.B. zur berprfung der ausgesendeten Daten)
Gesamtbersicht der Centronics Signale:(Signale mit * werden im PC invertiert)Pin     Pin      Bezeichng.       Datenr.      Fkt.PC      Druck.  1      1        Data Strobe      Ausg.        Datenbern.* 2      2        Datenl. D0       Ausg.         3      3        Datenl. D1       Ausg.        4      4        Datenl. D2       Ausg.        Daten fr 5      5        Datenl. D3       Ausg.        den 6      6        Datenl. D4       Ausg.        Drucker 7      7        Datenl. D5       Ausg.         8      8        Datenl. D6       Ausg. 9      9        Datenl. D7       Ausg.10     10        Acknowledge      Eing.        Druckbereit11     11        Busy             Eing.        Beschftigt*12     12        Paper out        Eing.        Kein Papier13     13        Select           Eing.        Ausgewhlt14     14        Autofeed         Ausg.        Zeile vor*15     32        Error            Eing.        Fehler16     31        Initial          Ausg.        Initialisieren17     36        Select Input     Ausg.        Auswhlen*18     19 .     . .     .         GND .     .25     30
Registerstruktur
     Adresse                                    Bit LPT1 LPT2 LPT3                7    6    5    4    3    2    1    0 3BC  378  278   Datenreg.    D7   D6   D5   D4   D3   D2   D1   D0 3BD  379  279   Statusreg.  Busy  ACK Paper Se- Error  X    X    X                              *         Out  lect 3BE  37A  27A   Steuerreg.    X    X    X    X   Se- Init- Auto  St-                                                 lect  ial  feed  robe                                                   *         *     *                         X = nicht benutzt
Programmierung
Ausgabe an HEX-Adresse 3BC (378):
Diese Instruktion bernimmt die Daten vom DB und legt sie an den Ausgabeport. (Strom liefern 2.6mA, Strom senken  24mA)
Ausgabe an HEX-Adresse 3BE (37A):
Die niederwertigsten 5 Bit des DB werden gelatcht. Bit 4 "1" Interrupt mit fallender Flanke (Pin10) Achtung: OC-Ausgnge, 4.7k an +5V, Imax=7mA
Eingabe von HEX-Adresse 3BC (378):
Liefert zuletzt ausgegebene Daten
Eingabe von HEX-Adresse 3BD (379):
Dieser Befehl liefert den Echtzeitstatus einiger Pins an. 
Serielle Schnittstelle
Aufbau einer RS232 Buchse
Smtliche Handshake-Leitungen der Schnittstelle sind direkte Ausgangsleitungen des UART 8250 und daher auch nominell ident mit dessen Pinbezeichnungen. Pinbelegung des UART 8250: 
8250-Pinbelegung
                               Netz und Rcksetzen                                                                                                                                                                                                                                       GND  MR  +5V                                                                                                                                                                                                                                          20   35   40                                                         D0-D7    1-8                                                                                                                                           Baustein-      CS0    12                  39    RI                                auswahl-       CS1    13                  38    RLSD                              leitung        CS2    14                  37    DSR                                                                         36    CTS                               Lese-/       DOSTR    18                               Hand-             CPU      Schreib-     DOSTR    19                               shake-            Schnitt  impulse      DISTR    21                               leitun-           stelle                DISTR    22                  34    OUT1   gen                                                                  31    OUT2                              Buffer-       DDIS    23                  32    RTS              serielle         steuer-      CSOUT    24                  33    DTR              Ein-/            leitung                                                          Ausgabe-                        ADS    25                                         schnitt-         Adressen        A2    26                                         stelle                           A1    27                  11    SOUT   serielle                                   A0    28                  10    SIN    Ein-/                                                                             Ausgabe                                INTRPT    30                                                                                    29 16 17 9 15                                                                     NC                                                                                  XTAL1                                                                               XTAL2                                                                                RCLK                                                                                BAUDAUT                                                                                                                                                                                                                                       Taktsignale                                   
PIN  BEZCH   NAME&FUNKTION           BEMERKUNG1-8 DO-D7   Datenleitungen           Verbindung zur CPU12 CS0      Bausteinauswahl          zur Bausteinauswahl hochgezogen)13 CS1      Bausteinauswahl          zur Bausteinauswahl hochgezogen)14 CS2      Bausteinauswahl          zur Bausteinauswahl heruntergezogen)18 DOSTR    Datenausgabepuls         wenn herunter geht, kann CPU Register beschreiben)19 DOSTR    Datenausgabepuls         wenn hoch geht, kann CPU Register beschreiben)21 DISTR    Dateneingabepuls         wenn herunter geht, kann CPU Register auslesen)22 DISTR    Dateneingabepuls         wenn hoch geht, kann CPU Register auslesen)23 DDIS     Datenausleseerkennung    geht herunter, wenn CPU Register ausliest)24 CSOUT    Datenausleseerkennung    geht hoch, wenn die 3 Adreauswahlleitungen aktiv)25 ADS      Adressimpuls             auf Masse wenn CS*,                                      A* whrend Datenbertragung gltig26 A2       Registerauswahl          auswhlen der Kanaladresse27 A1       Registerauswahl          auswhlen der Kanaladresse28 A0       Registerauswahl          auswhlen der Kanaladresse30 INTRPT   Interruptsignalisierung  geht hoch, wenn ein Interrupt ansteht)29 NC       keine Verbindung          16 XTAL1    Quarz-, Generatoranschlu Steuereingang fr internen Oszillator)17 XTAL2    Quarzanschlu            Steuereingang fr internen Oszillator) 9 RCLK     Empfngertakteingang     verbunden mit BAUDOUT15 BAUDOUT  16 mal Baudratenfrequenz20 GND      Masse                   35 MR       Rcksetzen aller Register40 VCC      Versorgungsanschlu (+5V)39 RI       Anruferkennung           bei Modem zur Anrufbeantwortung38 RLSD     Datentrgererkennung     bei Modem zur Trgersignalerkennung37 DSR      Sendeanfrage             mit DTR zB im Modem verbunden36 CTS      Sendeerlaubnis           Antwort zB im Modem auf RTS34 OUT1     Ausgangsleitung          im PC nicht benutzt31 OUT2     Ausgangsleitung          um Interruptanforderung auf INTRPT zu legen)32 RTS      Sendeaufforderung        33 DTR      Terminal betriebsbereit11 SOUT     serieller Datenausgang    10 SIN      serieller Dateneingang  
Der UART 8250 besteht fr die CPU aus 7 aufeinanderfolgenden Kanlen. Die Kanaladressen beginnen bei COM1 mit 3F8h und bei COM2 um 100h tiefer also mit 2F8h. Im folgenden Diagramm sind die Kanaladressen mit ihren Registerfunktionen aufgelistet (gilt nur fr komp. PC's): 
8250-Register
Kanal-    Register                      DLAB (<- Bit 7 im adresse                                 Leitungssteuerreg.[3FBh])3F8       Sende-Halteregister           03F8       Empfangs-Halteregister        03F8       Baudratenregister             1          (Lowbyte)3F9       Baudratenregister             1          (Highbyte)
Highbyte (3F9)      Lowbyte (3F8)              BaudrateBit0 1 2 3 4 5 6 7  Bit8  9 10 11 12 13 14 15   0 0 0 0 1 0 0 1     0  0  0  0  0  0  0  0       50   0 0 0 0 0 0 0 1     1  0  0  0  0  0  0  0      300   0 0 0 0 0 0 0 0     0  1  1  0  0  0  0  0     1200   0 0 0 0 0 0 0 0     0  0  1  1  0  0  0  0     2400   0 0 0 0 0 0 0 0     0  0  0  1  1  0  0  0     4800   0 0 0 0 0 0 0 0     0  0  0  0  1  1  0  0     9600   0 0 0 0 0 0 0 0     0  0  0  0  0  1  1  0    19200   0 0 0 0 0 0 0 0     0  0  0  0  0  0  1  1    38400
3F9       Interrupt-Freigaberegister    0          Bit0:             ermglicht Empfangsdateninterrupt:             Byte wurde empfangen und Bit0=1 -> Interrupt          Bit1:             ermglicht Sendedateninterrupt:             Sende-Halteregister entleert und Bit1=1 -> Interrupt          Bit2:             ermglicht Empfangsleitungsinterrupt:             spricht an bei Parittsfehlern, Aus-/Eingangssperre, usw          Bit3:             ermglicht Modemstatusinterrupt:             spricht an bei  Clear To Send  CTS                             Data Set Ready DSR                             Ring Indicator RI                             Received Line  RLSD                                      Signal Detect 
3FA       Interrupt-Erkennungsregister  X          Bit0: Solange Bit0=1, ist kein Interrupt vorhanden.                Wird Bit0=1, so kann man mittels Bit1,2 die                 Ursache ergrnden:          Bit2 Bit1 Bit0 Priorittsstufe Interrupt                           1    1    0    4 (hchste)     Empfangsleitung           1    0    0    3               Empfangsdaten               0    1    0    2               Sendedaten            0    0    0    1 (niedrigste)  Modemstatus
3FB       Leitungs-Steuerregister       X          Bit0,1:              Bit0 Bit1 Ausgabe             0    0    5 Bits             0    1    6 Bits             1    0    7 Bits             1    1    8 Bits                    Bit2:             Anzahl der Stopbits (Bit2=0 -> 1 Stoppbit                                  Bit2=1 -> 2 Stoppbits (1,5 bei                                   5 Bit Ausgabe))          Bit3:             Parittsbit (Bit3=0 -> kein Parittsbit                           Bit3=1 -> Parittsbit generiert)               Bit4:             Parittsbit nur wenn Bit3 gesetzt                         (Bit4=0 -> Paritt ungerade                          Bit4=1 -> Paritt geradzahlig)          Bit5:             generiert gemeinsam mit Bit 3 die Parittsbit-bertragung          Bit6:             Wenn Bit6=1, so wird der serielle Ausgang des WD8250             gesperrt.          Bit7:             DLAB
3FC       Modem-Steuerregister          X             Bit0: Bei gestztem Bit wird die DTR Leitung aktiviert.             Bit1: Bei gestztem Bit wird die RTS Leitung aktiviert.   
3FD       Leitungs-Statusregister       X             Bit0: - bleibt 0 bis ein Wort empfangen wird                   - ist das Wort eingetroffen und befindet                      sich im Empfangs-Halteregister so wird                     Bit0=1             Bit5: - bleibt 0 bis ein Wort, das in das                      Sende-Halteregister geschrieben wurde                     ausgesandt ist.
3FE       Modem-Statusregister          X             Bit4: zeigt Zustandsnderung der CTS Leitung an             Bit5: zeigt Zustandsnderung der DSR Leitung an             Bit6: zeigt Zustandsnderung der RI  Leitung an             Bit7: zeigt Zustandsnderung der DCD Leitung an
8250-Programmierung
1.In Turbo Pascal
program RS232_Senden;
uses crt;var  wert : byte;  ch   : char;
procedure oeffnen;  var  wert : byte;  begin     port [$3FB]:=128;     port [$3F8]:= 96;     port [$3F9]:=  0;     port [$3FB]:=  3;     port [$3FC]:=  3;           wert := port [$3F8]  end;
procedure schliessen;  begin     port [$3FC]:=  0;  end;
procedure senden (wert : byte);  begin     repeat     until port [$3FD] and 32 = 32;     port [$3F8] := wert;  end;
begin   clrscr;  repeat   oeffnen;   ch:=readkey;   wert:=integer (ch);   senden (wert);   writeln (ch);   schliessen  until ch = 'e';end.
2.In Assembler mit INT14h
   mctl   =  3fch       ;Modemkontrollregister   lstat  =  mctl+1     ;Leitungsstatusregister
   init: mov  ax,0e3h   ;9600 Baud, 1 Stoppbit; 0 Parittsbit; 8 Bit Wrter         int  14h       ;IBM BIOS         mov  dx,mctl   ;Kanaladresse Modemkontrollregister         mov  al,3      ;DTR und RTS Low gesetzt         out  dx,al              ret
   ausg: push dx                 push ax         mov  dx,lstat  ;Kanaladresse Leitungsstatusregister
   ausz: in   al,dx     ;Leitungsstatus holen         test al,20h    ;THRE (Bit5 Leitungsreg - =1: Sendebuffer leer)         jz   ausz      ;bei THRE=0 neuer Beginn         sub  dx,5      ;Kanaladresse Datenkanal         pop  ax                 out  dx,al     ;Zeichenausgabe         pop  dx         ret       
HC09SE1.C
.D.C:\MYLIB\SAMPLE\HC09SE1.C,/* HC09SE1.C *//*  * Serielle Schnittstelle via INT14h * ================================= */#include <dos.h>#include <bios.h>#include <stdio.h>#include <mytypes.h>VOID main(VOID){  union REGS r;  UINT port = 0; /* COM1 */  r.h.ah=0x00; /* INITIALISIEREN */  /* 110 BAUD, keine Paritt, 1 STOP, 8 Bits */  r.h.al=0x03;  r.x.dx=port;  int86(0x14,&r,&r);  printf("COM%i initialisiert, Status: %4x\n",    port+1,r.h.ah);  printf("Beachten Sie, "    "da an der Schnittstelle die Leitungen\n"         "2 - 3       TX, RX\n"         "4 - 5       RTS, CTS\n"         "6 - 8 - 20  DSR, DCD, DTR\n"         "verbunden werden mssen!\n");  printf("Zeichens von Tastatur an COM%i senden, "    "Ende mit ESC\n",port+1);  for(;;)  {    UINT c = bioskey(0x01);    if (c)    {      c = bioskey(0x00);      if ((c&0xff) == 0x1b) break;      // Senden      r.h.ah=0x01;      r.h.al=c;      r.x.dx=port;      int86(0x14,&r,&r);      printf("\nGesendet: %c; Status: %4x\n",c,r.h.ah);    }    r.h.ah=0x03; /* STATUS */    r.x.dx=port;    int86(0x14,&r,&r);    if (r.h.ah & 0x01)    {      r.h.ah=0x02; /* EMPFANG */      r.x.dx=port;      int86(0x14,&r,&r);      printf("Empfangen: %c\n",r.h.al);    }  }}.D.
HC09SE2.C
.D.C:\MYLIB\SAMPLE\HC09SE2.C,/* HC09SE2.C *//* * Zustand der Portleitungen testen * ================================ */#include <dos.h>#include <bios.h>#include <stdio.h>#include <conio.h>#include <mytypes.h>VOID main(VOID){  union REGS r;  INT c;  UINT port = 0; /* COM1 */  printf("\nTestet den Zustand der Portleitungen, "    "Ende mit ESC\n");  r.h.ah=0x00; /* INITIALISIEREN */  /* 110 BAUD, keine Paritt, 1 STOP, 8 Bits */  r.h.al=0x03;  r.x.dx=port;  int86(0x14,&r,&r);  printf("COM%i initialisiert, "    "Status: %4x, Modemstatus: %4x\n",    port+1,r.h.ah,r.h.al);  for(;;)  {    r.h.ah=0x03; /* STATUS */    r.x.dx=port;    int86(0x14,&r,&r);    if (r.h.ah & 0x01)    {      r.h.ah=0x02; /* EMPFANG */      r.x.dx=port;      int86(0x14,&r,&r);      printf("Empfangen: %c\n",r.h.al);    }    printf("CD  : %i\n",(r.h.al & 0x80) ? 1 : 0);    printf("RI  : %i\n",(r.h.al & 0x40) ? 1 : 0);    printf("DTR : %i\n",(r.h.al & 0x20) ? 1 : 0);    printf("CTS : %i\n",(r.h.al & 0x10) ? 1 : 0);    printf("DCD : %i\n",(r.h.al & 0x08) ? 1 : 0);    printf("DRI : %i\n",(r.h.al & 0x04) ? 1 : 0);    printf("DDTR: %i\n",(r.h.al & 0x02) ? 1 : 0);    printf("DCTS: %i\n",(r.h.al & 0x01) ? 1 : 0);    c = getch();    if ((c&0xff)==0x1b) break;  }}.D.
Teil 10: Tastaturprogrammierung
Klassen KBDQUEUE, KBD, KEYBIOS.HPP
.D.C:\MYLIB\INCLUDE\KEYBIOS.HPP,#ifndef __KEYBIOS_HPP#define __KEYBIOS_HPP#ifdef __cplusplus#include <memabs.hpp>/* class IBIOS * =========== * holds Pointer and Val of BIOS-Data-address */template <class T>class IBIOS : public BIOS<T>{public:  IBIOS(BIOSA offset) : BIOS<T>(offset)    { Old = Val; }  IBIOS(UINT offset) : BIOS<T>(offset)    { Old = Val; }  IBIOS(BIOSA offset, T t) : BIOS<T>(offset,t)    { Old = Val; }  IBIOS(UINT offset, T t) : BIOS<T>(offset,t)    { Old = Val; }  T operator () ()    { Old = Val;      Val = *((T far *)MKB_FP(*p));      return Val;    }  VOID operator = (T t)    { Old = Val;      *((T far *)MKB_FP(*p)) = t;      Val = t;    }  BOOL changed() { return (Old==Val) ? FALSE : TRUE; }  UINT pointoff() { return *p; }private:  UINT Old;};class KBDQUEUE : public BIOS<UINT>{private:  BIOS<UINT> *begin;  BIOS<UINT> *end;public:  KBDQUEUE(UINT adr) : BIOS<UINT>(adr)    { begin=new BIOS<UINT>(keybd_begin);      end=new BIOS<UINT>(keybd_end);    }  KBDQUEUE()  : BIOS<UINT>( *((UINT far *)BIOSA_FP(keybd_begin)) )    { begin=new BIOS<UINT>(keybd_begin);      end=new BIOS<UINT>(keybd_end);    }  ~KBDQUEUE() { delete begin; delete end; }  VOID set (UINT adr)    { p=(UINT far*)MKB_FP(adr);      operator()();    }  UINT next()    { p++;      if ( p==(UINT far *)MKB_FP(end->operator()()) )	p=(UINT far *)MKB_FP(begin->operator()());      return operator()();    }  UINT prev()    { p--;      if (p < (UINT far *)MKB_FP(begin->operator()()))	p=(UINT far *)MKB_FP(end->operator()()-2);      return operator()();    }  VOID operator = (UINT adr)    { BIOS<UINT>::operator = (adr);    }  UINT operator () ()    { return BIOS<UINT>::operator () ();    }  BOOL last()    { BIOS<UINT> head(keybd_q_head);      BIOS<UINT> tail(keybd_q_tail);      KBDQUEUE q(tail());      q.next();      if (q.off()==head()) return TRUE;      else return FALSE;    }};struct KBDSTATUS{  UCHAR flag1;  UCHAR flag2;  UCHAR flag3;  UCHAR flag4;  UINT head;  UINT tail;  UCHAR alt;  UCHAR brk;  UINT key;};#ifdef MYHISTORY  #include <myhist.h>#else  #include <queues.h>#endifclass KBD{private:  BIOS<UCHAR> *flag1;  BIOS<UCHAR> *flag2;  BIOS<UCHAR> *flag3;  BIOS<UCHAR> *flag4;  BIOS<UINT> *head;  BIOS<UINT> *tail;  BIOS<UCHAR> *alt;  BIOS<UCHAR> *brk;  KBDQUEUE *q;  KBDSTATUS Last;  KBDSTATUS Status;  BOOL Change;#ifdef MYHISTORY  KBDHISTORY *history;#else  BI_QueueAsVector<KBDSTATUS> *history;#endifpublic:  KBD();  BOOL read();  BOOL put();  KBDSTATUS *get ();  KBDSTATUS *operator () ();  BOOL changed() { return Change; }  KBDSTATUS last() { return Last; }  KBDSTATUS status() { return Status; }  UINT key() { return Status.key; }  VOID out(KBDSTATUS *s);  VOID out() { out(&Status); }  VOID out_history();};#else  #error "must compile with C++-Compiler#endif // __cplusplus#endif // __KEYBIOS_HPP.D.
Die letzten Tastenanschlge, HC10KB1.CPP
.D.C:\MYLIB\SAMPLE\HC10KB1.CPP,/* HC06KB1.CPP *//* * Die letzten Tastenanschlge * =========================== */#include <conio.h>#include <keybios.hpp>VOID main(VOID){  cout << endl << endl;  cout << "Reading last key, waiting for ESC\n";  BIOS<UINT> head(keybd_q_head);  BIOS<UINT> tail(keybd_q_tail);  KBDQUEUE endp(tail());  UINT c;  do  {    if (head()==tail())      cout << "nothing in kbd_queue\n";    endp.set(tail.val());    endp.prev();    cout << "HEAD : " << hex << head.val() << "  ";    cout << "TAIL : " << hex << tail.val() << "  ";    cout << "LAST : " << hex << endp.off() << "  "         << endl;    cout << "last value in kbd-que was : ";    cout << hex << endp.val() << endl;    c=getche();    cout << endl;  }  while (c!=0x1b);}.D.
Tastaturbuffer nach jeder Taste, HC10KB2.CPP
.D.C:\MYLIB\SAMPLE\HC10KB2.CPP,/* HC06KB2.CPP *//* * Tastaturbuffer nach jeder Taste * =============================== */#include <bios.h>#include <portable.h>#include <keybios.hpp>VOID main(VOID){  BIOS<UINT> head(keybd_q_head);  BIOS<UINT> tail(keybd_q_tail);  BIOS<UINT> beg(keybd_begin);  BIOS<UINT> end(keybd_end);  cout << endl;  cout << "Analyzing keyboard queue" << endl       << "========================" << endl       << "End with CTRL-BREAK" << endl << endl;  cout << "B:" << hex << (UINT)beg()       << " E:" << hex << (UINT)end() << endl;  KBDQUEUE q;  for (;;)  {    q.set(beg());    head(); tail();    if ( head.changed()||tail.changed() )    {      cout << hex << "H:" << (UINT)head()	   << hex << " T:" << (UINT)tail() << endl;      for (INT i=0; i<16; i++)      {	if (i==8) cout << endl;	cout << setw(4) << setfill('0') << hex << q();	q.next();	INT h=0;	if ( (beg()+2*i)==head.val() ) h|=1;	if ( (beg()+2*i)==tail.val() ) h|=2;	switch (h)	{	case 0: cout << "   "; break;	case 1: cout << "a  "; break;	case 2: cout << "e  "; break;	case 3: cout << "a+e"; break;	}      }      cout << endl << endl;    }  }}.D.
Tastaturbuffer fllen, HC10KB3.CPP
.D.C:\MYLIB\SAMPLE\HC10KB3.CPP,/* HC06KB3.CPP *//* * Tastaturbuffer fllen und anzeigen * ================================== */#include <portable.h>#include <conio.h>#include <keybios.hpp>VOID main(VOID){  cout << endl;  cout << "Fill keybd_queue, press 16 keys\n";  for (INT i=0; i<16; i++)  {    getche();  }  cout << endl << "Thank you \n";  cout << "analyzing now\n";  KBDQUEUE k;  for (i=0; i<16; i++)  {    cout << hex << setfill('0') << setw(4)	 << k.off() << ':'	 << k() << endl;    k.next();  }}.D.
nderungen des Tastaturstatus anzeigen, HC10KB4.CPP
.D.C:\MYLIB\SAMPLE\HC10KB4.CPP,/* HC06KB4.CPP *//* * nderungen des Tastaturstatus anzeigen * ====================================== */#include <keybios.hpp>#ifndef MYLIB#include "\mylib\source\keybios.cpp"#endifVOID main(VOID){  cout << "\nnderung des Tastaturzustandes anzeigen\n"       << "Ende mit CTRL-BREAK\n";  KBD kbd;  for (;;)  {    if (kbd.put())      kbd.out_history();  }}.D.
Analyse Tastaturbuffer, HC10KB5.CPP
.D.C:\MYLIB\SAMPLE\HC10KB5.CPP,/* HC06KB5.CPP *//* * Analyse Tastaturbuffer * ====================== */#undef MYHISTORY#include <keybios.hpp>#include <bios.h>#ifndef MYLIB#include "\mylib\source\keybios.cpp"#endifKBD kbd;VOID main(VOID){  cout << endl;  cout << "Testing keyboard related BIOS variables\n";  INT count=0;  do  {    if (kbd.put())    {      cout << hex << kbd.last().key << ' ';      count++;      if (count==12)      {	cout << endl;	count=0;	kbd.out_history();      }      if (bioskey(1)) bioskey(0);    }  }  while (kbd.last().key!=0x011b);}.D.
Analyse Tastaturbuffer, HC10KB5A.CPP
.D.C:\MYLIB\SAMPLE\HC10KB5A.CPP,/* HC06KB5A.CPP *//* * Analyse Tastaturbuffer * ====================== */#include <bios.h>#include <keybios.hpp>#ifndef MYLIB#include "\mylib\source\keybios.cpp"#endifKBD *kbd;VOID main(VOID){  kbd = new KBD;  cout << endl;  cout << "Testing keyboard related BIOS variables\n";  INT count=0;  do  {    if (kbd->put())    {      cout << hex << kbd->last().key << ' ';      count++;      if (count==12)      {	cout << endl;	count=0;	kbd->out_history();      }      if (bioskey(1)) bioskey(0);    }  }  while (kbd->last().key!=0x011b);}.D.
HC10KB5B.CPP
.D.C:\MYLIB\SAMPLE\HC10KB5B.CPP,/* HC06KB5B.CPP */#undef MYHISTORY#define __DEBUG 0#include <bios.h>#include <keybios.hpp>#ifndef MYLIB#include "\mylib\source\keybios.cpp"#endifKBD kbd;#ifdef MYHISTORY  KBDHISTORY history;#else  BI_IQueueAsVector<KBDSTATUS> history;//  BI_IQueueAsDoubleList<KBDSTATUS> history;#endifVOID main(VOID){  cout << endl;  cout << "Testing keyboard related BIOS variables\n";  cout << "Press some keys, end with ESC\n";  do  {    if (kbd.put())      kbd.out();  }  while (kbd.key()!=0x011b);  for (;;)  {    KBDSTATUS *s= kbd.get();    if (s)    {      kbd.out(s);    }    else      break;  }}.D.
Tastaturinterrupt umlenken, HC10KB6.CPP
.D.C:\MYLIB\SAMPLE\HC10KB6.CPP,/* HC06KB6.CPP *//* * Tastatur-Interrupt umlenken * =========================== */#ifndef __TINY__  #error Use TINY memory model#else#undef MYHISTORY#define __DEBUG 0#include <constream.h>#include <keybios.hpp>#ifndef MYLIB#include "\mylib\source\keybios.cpp"#endif#define INTR 0x9    // Tastatur-InterruptVOID interrupt (*oldhandler)(...);IBIOS<UINT> head(keybd_q_head);KBD kbd;const UINT STACKLEN=5000;static CHAR temporary_stack[STACKLEN];static UINT old_stackp;static UINT old_stacks;VOID interrupt handler(...){  oldhandler();    // alten Interrupt rufen  asm {    cli    // make TINY conditions cs=ds=es    mov ax,cs    mov es,ax    mov ds,ax    // remind old_stack    mov old_stackp,sp    mov ax,ss    mov old_stacks,ax    // make new_stack    mov ax,cs    mov ss,ax    mov sp,OFFSET temporary_stack    add sp,STACKLEN-1      }  UINT key=head();  if ((key & 0x0060) == 0x0060)    head = (key & ~0x0020);  else if ((key & 0x0040) == 0x0040)    head = (key | 0x0020);  kbd.put();  asm {    // get old_stack    mov ax,old_stackp    mov bx,old_stacks    mov ss,bx    mov sp,ax    sti      }}VOID main(VOID){  cout << endl;  cout << "Tastaturinterrupt umlenken" << endl;  cout << "Klein/Grobuchstaben werden vertauscht\n";  cout << "Ende mit ESC\n";  oldhandler = getvect(INTR);  setvect(INTR, handler);  for (;;)  {    getch();    if ((kbd.key())==0x011b) break;    kbd.out_history();  }  setvect(INTR, oldhandler);}#endif // __TINY__.D.
Tastaturbuffer-Pointer, HC10KB7.CPP
.D.C:\MYLIB\SAMPLE\HC10KB7.CPP,/* HC06KB7.CPP *//* * Tastaturbuffer-Pointer * ====================== */#include <conio.h>#include <keybios.hpp>VOID main(){  KBDQUEUE q;  cout << "+/- manipulates KBDQUEUE-pointer, "          "ESC terminates\n";  for (;;)  {    UINT key=getche();    if ((key&0xff)==0x1b) break;    if ((key&0xff)=='+')      q.next();    if ((key&0xff)=='-')      q.prev();    cout << ':' << hex << q.off()	 << ':' << q() << endl;  }}.D.
Tastaturrckgabefunktion, HC10KB8.CPP
.D.C:\MYLIB\SAMPLE\HC10KB8.CPP,/* HC06KB8.CPP *//* * Tastenrckgabefunktion * ====================== */#include <bios.h>#include <portable.h>#include <keybios.hpp>BOOL putback(UINT c){  BIOS<UINT> tail(keybd_q_tail);  KBDQUEUE q(tail());  if (q.last()) return FALSE;  q=c;  q.next();  tail=q.off();  return TRUE;}VOID main(VOID){  BIOS<UINT> head(keybd_q_head);  BIOS<UINT> tail(keybd_q_tail);  cout << endl;  cout << "Tastenrckgabefunktion" << endl       << "======================" << endl       << "End with CTRL-BREAK" << endl << endl;  KBDQUEUE q;  cout << "Tastenwiederholung, Ende mit ESC\n";  for (;;)  {    if (bioskey(1))    {      UINT c=bioskey(0);      if (c==0x011b) break;      q.set(head());      q.prev();      head=q.off();      if (bioskey(1))	cout << "Successfully echoed: "	     << hex << bioskey(0) << endl;    }  }  cout << "Tastensimulation\n";  q.set(head());  for (INT i=0; i<10; i++)  {    q.prev();    q=i+0x30;  }  head=q.off();  while (bioskey(1))    cout << hex << (CHAR)(0xff&bioskey(0));  cout << endl;  cout << "Tastensimulation mit putback\n";  for (i=0; i<20; i++)    if (!putback('A'+i)) break;  while (bioskey(1))    cout << hex << (CHAR)(0xff&bioskey(0));  cout << endl;  cin.get();}.D.
Beginn, Ende Tastaturpuffer, HC10KB11.CPP
.D.C:\MYLIB\SAMPLE\HC10KB11.CPP,/* HC05MM11.CPP *//* * Beginn und Ende des Tastaturpuffers * =================================== */#include <bios.h>#include <portable.h>#include <keybios.hpp>VOID main(VOID){  cout << "Beginn und Ende des Tastaturbuffers, "       << "bei jeder Tastenberhrung. "       << "Ende mit ESC\n";  IBIOS<UINT> head(keybd_q_head);  IBIOS<UINT> tail(keybd_q_tail);  do  {    head();    tail();    if ( head.changed() || tail.changed() )    {      cout << setw(2) << setfill('0') << hex           << head.val() << ':'	   << setw(2) << setfill('0') << hex           << tail.val() << endl;      if (bioskey(1)) bioskey(0);    }  }  while (head.val()!=0x011b);  cout << endl;}.D.
Analyse Tastaturbuffer, HC10KB12.CPP
.D.C:\MYLIB\SAMPLE\HC10KB12.CPP,/* HC05MM12.CPP *//* * Analyse Tastaturbuffer * ====================== */#include <bios.h>#include <portable.h>#include <keybios.hpp>VOID main(VOID){  IBIOS<UINT> head(keybd_q_head);  IBIOS<UINT> tail(keybd_q_tail);  IBIOS<UINT> beg(keybd_begin);  IBIOS<UINT> end(keybd_end);  cout << endl;  cout << "Analyzing keyboard queue" << endl       << "========================" << endl;  cout << "BEGIN : ";  cout << setw(2) << setfill('0') << hex       << beg.pointoff() << endl;  cout << "END   : ";  cout << setw(2) << setfill('0') << hex       << end.pointoff() << endl;  cout << "HEAD  : ";  cout << setw(2) << setfill('0') << hex       << head.pointoff() << endl;  cout << "TAIL  : ";  cout << setw(2) << setfill('0') << hex       << tail.pointoff() << endl;  for (INT i=0; i<16; i++)  {    BIOS<UINT>*q = new BIOS<UINT>(beg.pointoff()+2*i);    cout << "QUEUE adr ";    cout << setw(8) << setfill('0') << hex         << (ULONG)q->addr() << ':';    cout << setw(4) << setfill('0') << hex         << (*q)();    INT h=0;    if ( (beg.pointoff()+i)==head.pointoff() )      h|=1;    if ( (beg.pointoff()+i)==tail.pointoff() )      h|=2;    switch (h)    {    case 0: cout << " "; break;    case 1: cout << " head "; break;    case 2: cout << " tail "; break;    case 3: cout << " head+tail "; break;    }    cout << endl;  }}.D.
Analyse Tastaturbuffer, HC10KB13.CPP
.D.C:\MYLIB\SAMPLE\HC10KB13.CPP,/* HC05MM13.CPP *//* * Analyse Tastaturbuffer * ====================== */#include <bios.h>#include <portable.h>#include <keybios.hpp>VOID main(VOID){  IBIOS<UINT> head(keybd_q_head);  IBIOS<UINT> tail(keybd_q_tail);  IBIOS<UINT> beg(keybd_begin);  IBIOS<UINT> end(keybd_end);  cout << endl;  cout << "Analyzing keyboard queue" << endl       << "========================" << endl;  cout << "BEGIN : ";  cout << setw(2) << setfill('0') << hex       << beg.pointoff() << endl;  cout << "END   : ";  cout << setw(2) << setfill('0') << hex       << end.pointoff() << endl;  cout << "HEAD  : ";  cout << setw(2) << setfill('0') << hex       << head.pointoff() << endl;  cout << "TAIL  : ";  cout << setw(2) << setfill('0') << hex       << tail.pointoff() << endl;  for (INT i=0; i<16; i++)  {    cout << "QUEUE adr ";    BIOS<UINT> q = (beg.pointoff()+2*i);    cout << setw(8) << setfill('0') << hex         << (ULONG)q.addr() << ':';    cout << setw(4) << setfill('0') << hex         << q.val();    INT h=0;    if ( (beg.pointoff()+i)==head.pointoff() )      h|=1;    if ( (beg.pointoff()+i)==tail.pointoff() )      h|=2;    switch (h)    {    case 0: cout << " "; break;    case 1: cout << " head "; break;    case 2: cout << " tail "; break;    case 3: cout << " head+tail "; break;    }    cout << endl;  }}.D.
Head- und Tailpointer, HC10KB14.CPP
.D.C:\MYLIB\SAMPLE\HC10KB14.CPP,/* HC05MM14.CPP *//* * Head- und Tailpointer * ===================== */#include <keybios.hpp>VOID main(VOID){  cout << "Testing values of head- and tailpointer, "          "end with CTRL-BREAK\n";  BIOS<UINT> head(keybd_q_head);  BIOS<UINT> tail(keybd_q_tail);  UINT oldhead=head();  UINT oldtail=tail();  for (;;)  {    INT change=0;    head();    tail();    if (oldhead!=head())    {      change=1;      oldhead=head();    }    if (oldtail!=tail())    {      change=1;      oldtail=tail();    }    if (change)    {      change=0;      cout      << "H:" << hex << head() << ' '      << "T:" << hex << tail() << endl;    }  }}.D.

1Die hier beschriebenen Programme wurden zunchst mit BORLAND-C 2.0 geschrieben, Mittlerweile hat man bei BORLAND auch die Funktionsbezeichnungen von Microsoft 'ins Programm' aufgenommen, soda in diesem Punkt die beschriebenen Makros entbehrlich geworden sind. 
܀   )h  Rm  w t  t  u   	  q ߎ    o <  =  m     k     i I  J  g   77777 `7     y >  ?  w     u ƕ  Ǖ  q ܕ  ݕ  o     k [  e  f     a K   ` ` `7  777K  W  v ѝ  ۝  q   "  o <  D  m     k     i      g h  q  e v   ` ` `v    y     w     u @  ^  s ?  M  q K  Y  o   u  j     h     f  `      y   !  w 4  ;  u E  L  s _  f  q     o     m     k       y 7  >  w U  \  u o  v  s     q     o     m     k     i     $  y 6  =  w L  S  u a  h  s     q     o     m     k       y     w $  +  u I  P  s n  u  q     o     m     k     i   	    y   "  w *  1  u >  E  s U  \  q z    o     m     k       y     w     u     s A  H  q n  u  o ~    m     k     i       y     w     u     s 1  8  q L  S  o   "  m V  \  k Y  Y  ^  y c  h  w u  w  u |    s     q     o     m     k     i       y     w 
    u     s 
    q     o k  m  k     i   7 `    y     w      u     s     q     o )  *  m 1  2  k >  ?  i 777777777?  F  G  y S  T  w [  \  u h  i  s p  q  q }  ~  o     m     k   777777777    y     w     u     s     q     o     m .  0  i   g  `7777777 
 
 y   r , , p *, 3, n o, r, l y, , j v6 6 e ; `   ` ! `  ; ; v 1? q h? j? m k? w? i @ e @ a A hG ] xG ?K Y BK W 6 e ; ` ` ! ` ! ` ` !
BK GK w KK u T q T [ m [ \ i \ g \ c \ a 5] ] =] [ s] W ] U  ` ` ` ` ` ` `] ] w ] _ s _ q _ m _ k n` g x` e }` a ` _ a [ (a Y ,a U ca  ` ` ` ` ` ` `ca b w c c u ,d =d s Kd dd q d f m f k g g Fg b g >h ^ zh  ` `   ` ` `zh h w h u j q j o j k j i +k e zk k c l &l _ (l l [ l Y zh  `   ` ` ` `l 5m w m m u m m s m m n p p l p p j /q 9q h q q f q      `q q y q q w u u u Vv `v s v v q zw w m w sx i x g{ e |    ` ` `| } w } u j q s o  k   g `  c  a  ]  [  W r g{ ` ` ` b ` ` `r 0 v C r  m   i 3 
 e  . a l 8 ] > [ a W i U r g ` ` ` ` ` ` ` `
i  w  > s ] C o k  k   g  e Ǩ a Ԩ _  [ ; g ` ` ` ` ` ` ` `;  w 	 k s  n [ ] j   h   f   d L T b ſ  ` ` ` ` `ſ ɿ w   s  " o E I k c g g   b   ] ! ſ         ! 1 v  	 q d u l   h  H d S b  ^  \ ' X  ſ  ` ` `       y   w   r   p   n  l _ g j   h   f      y   w   u   s   q   o   m   k 	  i    y # + w - 2 u [ c s   q   o   j < E h @   @ R y 4 7 w   u   s   q   " o ' * m , 2 h   f      y   w  # u   s 	  q   o $ 4 m s | k C   C H y   w C b r   m  i  d 4 ` 7 [ 9 W X R 4                 
X  w  r  n  i   d  `  [   V  R  N   `                  
  y   w   u l  p S
 V
 k X
 f e
 q
 d 
 
 b 
 
 `       
 
 
 y   u    q + ; m < L i w  e   a  
 `         w   s 
 
 o 0 1 k   i W b g |  e   `          v  q   o   m   k   i M  O  g p    e     c         	! ! y ,! -! w .! 0! u a! b! s c! e! q n! r! o ! ! m ! ! k ! 7777777! ! y ! ! w ! ! u ! ! s " " q " " o  " %" m <" K" k L" N" i 777777777N" W" [" y s" " w " " u " # s l# n# q # $ o *$ 3$ m M$ W$ k w$ 7777w$ $ y $ $ w $ $ u D% N% s % % q % % o F& L& m & & k & & i & 7' =' y ( ( w h) m) u ) ) s ) * q + + o - - j J J e & i    J J w J J u "K %K s AK DK q PK RK o K K m K K k K K i GL _L g    _L dL jL y M M w M M u $N )N s [N `N q N N o N N m ~O O k P  P 1P y RP YP w ^P fP u P P s P P q P P o vQ Q m Q Q k R R i  R )R 0R y 2R :R w @R GR u R R s R R q S 'S o S S m S S k S  S S y ;T =T w T T u U 
U s U !U q U U o U U m U V k V V i  V 'V 1V y AV KV w W W u X X s XX lX q X X o X  Y m Y Y k Y  Y  Z y %Z *Z w Z Z u Z Z s Z Z q Z Z o K[ Q[ m n[ q[ k [ [ i  [ \ \ y \ \ w \ \ u &] (] s ^ ^ q 
_ _ o ;_ D_ m P_ Q_ k {_  {_ ~_ y _ _ w _ _ u _ _ s a !a q 3a 8a o =a Ca m a a k gb ib i  ib b b y c c w >c ?c u Oc [c s `c gc q c c o nd xd m d d k d  d e y e e w ne se u f f s of wf q f f o f f m f f k ]g bg i  bg i i y i $i w i i u i i s i i q lj rj o wj xj m Xk bk k uk  uk zk y k k w k k u Jm Om s o o n q q i r r g r r e *r uk     *r 8r y =r Cr w Jr Pr u r r s r r q r r o r r m r r k s s i s s s y )s 6s w ;s As u s s p Fy Iy k Wz wz f | | a | ] s          | } #} y } } w } } u } } s H~ i~ n > A i S  g   e        Z f y   w   u   s j q q {  o   m   k      y   w   u   s   q 1 4 o 9 > m  ! k _ a i a f h y   w   u  
 s   q = B o Ŋ ˊ m Њ ӊ k    y   w 7 W r   m  i   g  ' e 7 ? c v ~ a       ~ ^ r y z { w   u   s   q j t o i p m   k ޘ  ޘ  v   q % ' o r w m   k  ؟ f   a   _  k         ± ˱ y ڱ ߱ w 5 U r   m   k   i T ^ g x  e          y   w   $ u * / s h m q   o Z _ m n v k ~ ~  v   q D Q l   g   b 0 P ]   X * n               * J v / 4 q   o $ + m 0 8 k k  i   g   e         % y   w   u q  p m p k   g    c  b _     `    b   w  	 s    o   k  	 g : ; c ] ^ _            w  ! r J M m O h  ֋ c f k ^ 1 3 Y S T      `     !        v P q q   l 2 R g   b  ] ( G X         !          
   v / O q   l  g   b c f ] h X  V ͱ  T    `             , 4 y   w   u   s   q   o   m   k a   7777777a p y   w Ķ  r   m   h   c 
 * ^ w             w z v   q w z l   g Y \ b   ]   X                   v O R q T m 0 N h < A c   ^ K P Y u  T                  o r v t q  F l   g   b c h ]  2 X               `    v   q f k l  . g " " b # .# ] P& U& X x&                 x& & v ) ) q * -* l n4 s4 g 4 4 b > > ] I 	I X x&                 
	I 'I v K K q  L L l 
Q Q g Q Q b S S ] T  T X V      `   `   `  V V v V V q l[ q[ l }[ [ g _ _ b _ ^ _ Y e e T V  `   ` `   `   `
e e w e r g g m g i g d j j _ j [ j V n n Q } V `   ` `   ` `   `
} } v b e q g l  = g ŏ ȏ b ʏ ] ! ? X q t S V                 
t v v   q   l , J g R W b   ] R U X b            `    b  v \ a q l  l x } g   b [" ^" ] " " X %          `      % % v % % q g* j* l * * g , , b , , ] w. z. X .                 . . v 0 0 q 1 @1 l y3 |3 g 3 3 b %6 (6 ] S6 s6 X e<                 e< h< v < < q j> m> l > > g C C b C C ] F F X F                 F F v PL SL q {L L l Q Q g R 6R b T T ] T T [ U                       v    v    q    l   g   b   b %  b  *T *T             2)  2)  2)  2) = Y = ?  %  0  v G  q    l 
  l y  l ]  l   l ,  l  2) 2)  
2)  2)  2)  
2)  2)  2) = Y =   ,  D  v   q   q !  l   l   g   g     b  2)  2)  2)  2)  2)  	2)  
2)  2) = a =         v #  q 5#  l $  g E&  g e(  g T)  g .*  g  2)  3) 2)  2)  2)  	2)  2)  2)  =  K = .*  K*  v *  q *  l F+  S +  S +  S  2)  2)  2)  2)  2)  2)  2)  	2)`0  Q         a =  +  ,  b s,  b m-  b S0  ] 2  ] _4  ] z4  X  2)  2)  2)  
2)  2)  2)  2) `0 = `0  Q         z4  6  v 9  v ?9  q )<  l J<  g g<  b <  ]  
2)  2)  2) 
2)  2)  2)  2) `0 a   =  = <  O=  v =  v =  q >  l q?  l ?  g @  b  2)  2)  2)  2)  2)  2)  2) `0 a =  =  a @  @  v B  q B  l C  g 4C  b vD  ] D  X  2)  2)  2)  2)  2)  2)  2) `0 =  =  =  D  ZF  v TH  v _I  v wI  q I  l [J  g K  b  2)  	2)  2)  2)  2)  2)  2) `0 = = a =  = K  
L  v RL  q kL  l N  g O  g O  b Q  ]  2)  2)  2)  2)  2)  2)  2) `0 =  =  = a Q  S  v /S  q T  l U  l :Y  l Y  l Y  g Z  b  	2)  2)  2)  2)  2)  2)  2) 2) =  =  = Z  UZ  v Z  ] Z  ] O[  ] [  ] y\  X ]  X  2)  2)  2)  2)  2)  2)  2)  2)= `0  Q         a ]  ^  v `  v 4b  v :d  v 6e  v g  v )h  q Rm  l  2)  
2)  2)  	2)  2)  2)  2) 3) 0  Q  A  = Rm  m  v n  v p  v Qq  v r  v 
u  v u  v u  q !w  l  2)  2)  2)  2)  2)  
2)  2)  2) 2) =  = 	!w  x  v y  v <{  v }  v }  v }  q ~  l ~  g  	2)  2)  2)              *T             2) = ?  = ~  B  v V  q   l   l   l   l   l   l  2)  2) 2)  2)  2)  2)              2) = =  Y   	  v   i   d   _ J  Z p  U  *Tx  +*T              2)  2)  2)  2)   Y = W J %  7  W p  [  v ]  U e  8 u  8  2)  2)  2)  2)         F #  @           @ F #  @         t    @= u    ^   ^ ̚  ^   ^   ^   ^ 9  ^  2)  2)  2)  2)  2)  2)  2)  F #  @F #  @           @9  j  ^   ^   ^ ˛  ^   ^ 
  ^ +  ^  2)  2)  2)  2)  2)  2)  2)  F #  @F #  @           @+  K  ^ u  ^   ^   ^   ^   ^ ۜ  ^  2)  2)  2)  2)  2)  2)  2)  F #  @F #  @           @ۜ    ^   ^ %  ^ I  ^ K  ^ W  ^ j  ^  2)  2)  2)  2)  2)  2)  2)  F #  @F #  @           @j    ^   ^   ^ ϝ  ^ ѝ  ^ ۝  ^   ^  2)  2)  2)  2)  2)  2)  2)  F #  @F #  @           @    ^   ^   Y   T I  O   O  2)  2)  2)  2)  2)  2)  2)  =  = F #  @           @    v ӥ  v   q ʧ  l ]  l ;  l   l   l g  l  2)  2)  2) 	2)  2)  2)  2)  2)  2) =  = 	g    v Ӱ  v   v   v B  v f  q   l   l $  l  2)  2)  2)  2)  2)  2)  2)  2)  2) =  = 	$    v   v   v ҽ  q   l "  l   l   l   l  2)  
2)  2)  2)  2)  2)  2)  2)  2) =  = 	  <  v ;  q G  q   q   q u  q   l  2)  	2)  	2)  2)  2)              2)  2)  2)  2)  = G     Z   Z   Z   Z   Z   Z   Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p    Z /  Z D  Z Z  Z   Z   Z   Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p    Z 6  Z T  Z j  Z   Z   Z   Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p    Z   Z   Z   Z   Z   Z 1  Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p1  K  Z `  Z   Z   Z   Z   Z   Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p    Z #  Z H  Z m  Z   Z   Z   Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p    Z   Z   Z )  Z 9  Z T  Z y  Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    py    Z   Z   Z   Z   Z   Z @  Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p@  m  Z }  Z   Z   Z   Z   Z   Z  2)  2)  2)  2)  2)  2)  2)  2)  F #  p         X    p    Z -  Z G  Z b  Z   U   U  2)  2)  2)  2)  2)  
2)  2)  2)  =  F #  p         X    p    v   q   l u  l g  l   g   b   b  2)  2)  2)  2)  2)  2)  	2)  
2)  p=  =       v ^  q   l   g   g   g   g   b  2)  2)  2)  	2)  2)  
2)  2)  2)| pA =  =    j  v k  v m  q n  l .  _ 0  Z 1  U                          4*T              = W J %    = W = 1    v   q   V  I              *T                          4*T  @     Z  7        7N= Y  % ` -
 F > F 8 F ? F  2)  2)  	2)  2)  2)        4*T<              @[  7         7N?  a  a 7 a B \ M W M W 
 W  2)  2)  2)  2) 2)  	2)  2)    =  <              @
  v  q  l  l  l  g d b  b  2)  2) 2)  2)  2)  3) 2)  2) = A =  =   v @  q L  q ! P  3) 2)  2)  2)  2)  2) 2)  2)  2)   F #              H    	= A ! #! ` B! [ _$ V % V ' V ( V n+ V        2)  2)  2)  2)  2)  2)  =  Z  7         7Nn+ , v H. v o. q C0 l d0 g  2)  2)  2)  2)  2)  2)  2)  2)  2)  =  Z   =  = d0 |2 V 5 Q 5 Q v6 L 6 G  2)  2)  2)  2)        2)  2) G = $F #              X      6 8 v 9 v ; v ; v ; q = l 1? l 2? l                                                         =  = 2? h? v j? q k? l w? Q @ L                    2)  2)                 = Z  7         7N= W  @ @ v @ q B l D l D l hG l xG g I b  2)  2)  2)  2)  2)  2)  2)  
2)   =  =   I J v J v nK v _M v 2N v O v DQ v dR v T v  2)  2)  2)  2)  2)  2)  2)  2)  2) =  = 	T T v U q +W q 9X q Y q [ q [ l e\ g  2)  2)  2)  2)  2)  	2)  2)  2)  2)  =  =  e\ ] v ] q ^ l ` l ,a l Ia g b b &d b  2)  2)  2)  2)  2)  2)  2)  2)  =  =  = &d fd v }d q f l g l g g >h b zh ]  2)  2)  	2)  2)  2)  2)  2)  2)  2 =  =  = zh #i v -k v k v l q (l l 5m g Sm b  2)  	2)  2)  2)  2)  2)  2)  2)  2  =   = Sm m v n q tp l 6r l |u g Cw b `w ]  2)  	2)  2)  2)  3) 2)  2)  2)  2 = A =  = `w w v w q sx l x g y b y b 
z b g{ b  2)  2) 2)  2)  2)  2)  2)  2) =  =  = g{ | ^ } Y ~ Y  Y  Y ݀ T  T  
2)  2)  2)  2)  2)  2)  2) = a @                   w v  v  v  v  v  v  q R l  2)  2)  2)  2)  2)  2)  2)  2)         =  = R r v ё q   q 3 l 
 g  b . ]  2)  2)  2)  2)  2)  2)  2)  2)  2=  =  =  . l v " q  q  l > g ] b C ]  2)  2)  2)  2)  
2)  2)  
2)  2)  2=  =  =  C k v  q , q  q  l ~ g  2)  2)  	2)  2)  2)  2)  2)  
2)  2)  2=  =  =  ~ ֨ b | ]  ] ; X  S 	 N  2)  2)  2)  2)  2)  2)  2)  =  = <              @	 k v X v Y v z q  l η j з j [ j  
2)                    *T  *T  *T  *T <     = [ ] v ^ q  l ! g  b F b V ]              2)  2)  2)  2)  2)  *T  *a =  Y = W V t b Ǽ b ռ b O ] T ] g X  2)  2)  2)  2)  2)  2) 2)  2)  *T = `0  Q         g  v  q  q  l ' g 8 b  ]  2)        3))       2)  2)  2)  *T Y  Y   =   v  q d q  l z g  g  b ) b  	2)  2)  2)  3) 2)  2)  2)  2) a = A =  )  v  v ( v s v  v  v  v  v % q  2)  2)  2)  2)  2)  2)  2)             A  a 	%  v " v . q  l  g  g  b  2)  2)  2)  2)  2)  2)  2) 2)  2)    = Y  =  " v . q  l 7 S D N  I  2)  2)  2)  2)        2)  Y  @                Y  =   v , q  X  S ^ N i I  2)  
2)  2)  2)  2)  	2)  = Y  @                Y  i w v C q  l 9 l  l  l  l  2)  2)  3)4 3)h 3)             @              A Y    a  \  W l R X
 M 
 H              2) 2)  3) 2) = A =  = <             @
  v  q 
 l  g  g  g  b  2)  2) 
2)  2)  2)  2)  2)   = G =   =   v  q  l  g  b  b        3) 
2)        2)  2)  2)  2)   = =  = A =   b  ] 	! ] " X & S ' S  2)  2)  3) 3), 2)  2)  2)  2)=  = @                ' ' v ( v ) v * v T* v * v * v %+ v , v  2)  2)  2)  2)  2)  2)  2)  2)  2)        a 	, , v , v - v - v J q M l N l N g  2)  2)  2)  2)        
2)  2)  2)  2)   = A a N P v jQ q R q +S q S q T q U q U q sW q  
2)  2)  2)  2)  2)  2)  2)        2) = = Y 	sW W v X q Y l Y g .Z b eZ ] Z X  2)  2)  2)  2)  2)  2)  2)  2)=  =  = Y  Z [ v _[ q [ q [ l \ g \ b ^ ]  2)  2)  2)  2)  2)  2)  2)  2)= =  =  =  ^ ^ v 	` q ` l a g b g d g Id b  2)  2)  2)  2)  2)  2)  2)  2)= =  =  =  Id Od v cd q e l e g e g e b e ]  2)  2)  2)  2)  2)  2)  2)  2)=  =  =  = e i v Mk v .l v Rl q o l q g s b  2)        2)        2)  3) 	2)  2)=  = A =  = s Ky v Ly q Ny l Oy g y b Wz ] | X                          2) 2)  3)x 2)A =  = W = A | ~ v H~ v C q S l  S o N  2)  2)  3)` 2)  2)  2)  2) = E              p= A = o  v R q _ l 9 g m g 7 g  b  2) 2)  2)  2)  2)  2)  3))E       A =  =   & v Z q  q ޘ q  l  g > b  2)       2)  	2)  3)h 2) 2) E       =  A =  > n v  q  l  g K b q ]  ]  2)        3)9       2) 2)  2) E     =  = A =   Q v  q  q  q 5 q  l  l  g  3) 2)  2)  2)  2)  3)              A =   s v Q q  l  l ' l } l  g  b  2)  3) 2)  2)  2)  2)              A =  =   v / q  l  g  b  ]  X  2)  3)              2) 
2)  3)\
     =  = A  =   v * v 4 q  l ) l 7 g q b  2)  2)  3)
 2)  2)  2)  2)      = Y  = A = q r v n q  l  g  b  ]  3) 	2)                          2)  2)      = W =  = A   n : i   i  i b i } i 
 d  2)  2)  2)  2)  2)  2)  2)      = WG  E      
  v  v  v 	 v 
 q V l  g  b  2)  2)  2)  2)  2)  2)  2)  2)  =  =    v S v T q I q g l  g 2 b  
2)                    2) 2)  2) 2)  2)  =  =  2  v  v O q h l e g } g  b  2)  2)  3) 2) 2)  2)  2) 2)  2)  =  A =    v !! q $ l $ g 	& b < b j= b @ b  2)  2) 
2)  2)  3)	 3)P 3) 3)\
  =  = @ E v K v K v <L v M v O v O v zP v Q v  3)0 3)L 2) 2) 3)X 3)@ 3) 3)( 3)@=   	Q KR v R v FW v X v |Y v Z v [ v ^ v uc v  3) 3) 3)0 3) 3) 3)( 2) 3)\
 3)0=   	uc d v !e q /e l re g e b e b e b  3) 2)  2)  2)  2)| 2) 2)| 3)( 2)  =  =  e f v h q h q %j q l q n q r q v q tw l  2)  3)X 3) 3)@ 3) 3)
 3)\
 3) 2) =  = 	tw w v Jx q y l z g nz g { g V| g Ղ b  2) 2)  2)  2)  2)  2)  2)  3)  = G =  Ղ # v A v L q - l  l  l  g  b  2) 2)| 2)  2)  2)  2)  2)  2)  =  =     v Њ v  q  l k g u b  b  b  2)  2)  2) 2)  3)L 2)  2)  2)  = A =  =  * v 3 q  l P g  b  ] : ]  2) 2)  3)  2)  3) 2)  2)  2)  2= A = A =  :  v 2 q  l ( g  b / ]  X  2)  2) 3) 2) 3)` 2) 3) 2)A  A  A  =   v  q h l = g W b  ]  2)  2) 3) U 2)  2)  2)  2) 3) 2)A =  = A  =   b  ] q ] ~ X ĳ S  N  3) 2)  2)  2)  	2)  
2)  2)=   = E               Ķ v  q  l  g 
 b | ]  X  2)        2)  3) 2)  3)< 2)   A  A  A   | v  q ^ l _ g  b  ]  X  3)% 2)              2) 2) 2)  =   = A  A  ? v N q d q p q  q  q a q } q  q  3)p 2)  2)  2)  2)  2)  2)  2)  2) A = K 	  v  q : q  q  q T l  g 0 b  3), 2)  2)  2)  2)  3) 2) 2)  =  A = A 0 A v h q  l P g R b 
 b  b g b  3) 2) 
2)  3)@) 2)  2)  2)  2)  = A =  A g  v ` v u q t l { g  M  2)  2)  2) 3)h 2) 2)  2)<              @ A  =  ( v  q  l  l  l > l p l  g        3)& 2)  2)  2)  2)  2)  2)   @ = A    v h q  l  g  b  ]  X  2)  3) 2) 2)  3)d 2)  2) 2) = A =  A =  k v  q  l " g # b U& ] x& X  3) 2) 2)  3) 2) 3) 2) 2) A  A =  A x& ) v * q s4 l 4 g > b K@ ] \@ X  3)(( 2) 3)D 2) 3)B 2)  2)  2) = A  A  A \@ @ v  A q A l A l A l B l B l ;B g  2)  2)  2)  2)  2)              2)A  =  = ;B [B v ?D q IH l 	I g K b K ]  L X  2) 	2)  3) 2)  3) 2)  2)     = A = K =   L Q v Q q Q l S g T b V ] V X  3)% 2) 2)  3)0 2) 3)H 2)     A  A =  A V q[ v }[ q _ l _ g e b e ] g X  3)h 2) 3) 2)  3) 2 2)  3)    A  A  A  A g g v j q j l n g n b $p ] q ]  2)  3)x 2)  3)| 2) 2)  2)     A =  A  A  q r v s v s v Nu v u v v v w v x v Zy v  2)  2)  2)  2)  2)  2)  2)  2)  2)  A = 	Zy z v | v } v g q ΅ l n l ? l q g  2)  	2)  2)  3)* 2)  2)  2)  2) 2)   = A = q 4 v  v ʏ q B l  l ! l v g O b  2)  2)  3) 2)  2)  2)  3)G 2)  = A = A = O l v  q  l  g ߲ b  ] , X  2) 2) 3)O 2)d  2)  3) 2) 3) K = { A   , W v w q  l W g f b  b  3)o 2) 2) 3)< 2)       3) 2) 3) K  A   A   ^ a Y  Y   Y L Y  Y  Y  2)  2)  2)  2)  2)  2)  2)  3)= Z  7        7N  v  v  v  v < v  q  l  g  2)  2)  2)  2)  2)  2)  2) 2) Z  7   =   v  q  q  q B q f q  q  q  q  2) 2)  2)  2)  2)  2)  2)  2)  2)  =  	  v  q  l  g  b ( b : ]  2) 2) 2)  2) 22)  :2)  2)  2)  2=   =   : I v  q  q  q  q B q  l  g  2) 
2)  
2)  2)  
2)  #2)  2)  
2)   =     v  q  l  l  g  g ! g  g  2)  2) 2)  2)  2)  2)  2)  2)   =    3	 v N	 v 	 v 
 v  v V v W q b l  2)  2)  2)  2)  2)              2)   =  b a v l q } l  g  b `" ] " X  3)! 2) 3)P 2) 2) 3)`Y 2)     A   A  A " % v % q l* l * g , b , ] |. X  3)x 2) 3) 2) 3) 2) 3)
    A  A  A  A |. . v 0 q 1 l ~3 g 3 b *6 ] S6 X  2) 3) 2) 3) 2) 3)| 2)     A  A  A  S6 j< v < q o> l > g C b C ] F X  3)\0 2) 3) 2) 3)% 2) 3)    A  A  A  A F F v UL q {L l Q g R b T ] T X  2) 3)h 2) 3) 2)                = A  A  A  T T U y U                   T X  2) 3)h 2) 3) 2)                = A  A  A  O  ^ VT _U _U M 
       2  J  .d  }         &    |   > `V l  y ڵ    e K - > )U k } ; g C     % D x^ u ه  ' Ь : Ӿ n   L  ] % ) 3 z= uK V :_ Nj ^}  &      Q 8    *, 5 $C pQ _U       F            d                  *                 	      
       )           
 5      K                 E                        l           4           K          #                 '      1       0     ! M    "      #      $     %     & }    ' ^    (      )     *     +     ,     - T    . 	    /      0 )     1     2     3      4     5 d
    6 D
    7      8     9     :     ;     <     = 0    >     ? r
    @ H    A D    B 	    C      D     E     F 
    G     H Z    I     J @    K +     L     M     K    i}     }       d                 |  >   >  ٵ  
 ޷  d   x   x             A      TT   `U        " *         10.25.9202.09.92_U 