====== AES-Programmierung ======
AES steht für Application Environment Services, sie sind Bestandteil von Portfolios Betriebssystem und im BIOS-ROM gespeichert.\\
Die AES sorgen für die Ausführung der Portfolio-spezifischen Funktionen wie Benutzeroberfläche, Wählfunktion, Kartenformatierung u.s.w. Sie werden über die Interrupts 60H und 61H aufgerufen.
Eine komplette Übersicht ist im Technischen Referenzhandbuch zu finden, im Folgenden werden Beispiele für die Programmierung der wichtigsten Funktionen aufgeführt:\\
* Zeilen-Editor\\
* Menüs\\
* Meldungen\\
* Wählton\\
* Melodie-Töne\\
* DAT-Dateien FIXME\\
===== Zeilen-Editor =====
Der Zeilen-Editor wird mit der Funktion 01h des Interrupt 60h aufgerufen, vorher muss aber eine Datenstruktur definiert und initialisiert werden, dort sind die Werte für Bildschirmposition und Darstellung, sowie viele, für die Funktion wichtige Daten enthalten. Im Technischen Referenzhandbuch wird folgende Struktur angegeben:\\
EP_TARG DW 2 DUP (?) ;Adresse der Zeichenkette
EP_POS DW (?) ;Position innnerhalb der Kette
EP_MAX DW (?) ;max. Länge der Kette
EP_XPOS DB (?) ;Cursor-Position. Spalte.
EP_YPOS DB (?) ;Cursor-Position. Zeile.
EP_MODE DB (?) ;Zeichenkette löschen?
EP_HIT DW (?) ;Pre-Processing
EP_TIT DW 2 DUP (?) ;Adresse der Überschrift und Eingabeaufforderung
EP_EXIT DW 2 DUP (?) ;Adresse der Liste für Ausstiegstasten
EP_FN DW 2 DUP (?) ;Adresse der Routine für Tastaturabfrage
EP_WID DB (?) ;max. Länge des Editier-Bereichs
EP_WIND DB (?) ;Rahmen-Typ (0FFh=keiner,0=einfach,1=doppelt)
EP_RES DW 2 DUP (?) ;reserviert! Nicht verändern.
EP_UDEL DW 2 DUP (?) ;Adressse der Undelete-Routine
\\
Der Zeileneditor erwartet, dass alle Doppelwort-Zeiger mit dem niederen Word voran angegeben sind, das heißt erst der Offset, dann das Segment (OFS:SEG).
==== Datenstruktur ====
**EP_TARG** ist ein Doppelwort-Zeiger auf die Adresse der zu bearbeitenden Zeichenkette in der Form OFS:SEG.\\
Beispiel:\\
EDSTR DB "Diese Zeile bearbeiten",0
.
.
.
MOV EP_TARG[0],OFFSET EDSTR
MOV EP_TARG[2],CS
Die zu bearbeitende Zeile muss immer NULL-terminiert sein. Das heißt auch, dass man für die Speicherung der Kette immer ein Byte mehr reservieren sollte.\\
\\
\\
**EP_POS** ist ein Versatz innerhalb der Zeichenkete an der sich der Cursor beim Aufruf befinden soll. Das Editieren erfolgt dann ab dieser Stelle.\\
Beispiel:\\
MOV EP_POS,6
Der Cursor steht im oberen Beispiel nach dem Aufruf auf dem "Z" (das "D" ist Stelle 0).\\
\\
\\
**EP_MAX** ist die maximale Länge die die Zeichenkette während der Bearbeitung erreichen kann. Damit diese nicht überschritten werden kann, blockiert der Zeilen-Editor die Eingabe, außer für Entf, die Rücktaste und für die in der Liste der Austiegstasten definierten Tasten-Codes.\\
MOV EP_MAX,20
Die Zeichenkette ist somit auf 20 Zeichen begrenzt, es können keine weiteren Zeichen eingefügt werden, es sei denn man löscht vorher Zeichen mit Entf oder der Rücktaste.\\
\\
\\
**EP_XPOS** und **EP_YPOS** enthalten die Werte für die Bildschirm-Position des Eingabebereichs (mit oder ohne Rahmen). EP_XPOS muss zwischen 0 und 39 (Spalte), EP_YPOS zwischen 0 und 7 (Zeile) liegen. Die beiden Werte definieren die linke obere Ecke des Bereichs, der Programmierer muss dafür sorgen, dass die Werte nicht überschritten werden. Das Bearbeiten einer 10-stelligen Zeichenkette sollte z.B. nicht bei EP_XPOS größer als 29 beginnen, es sei denn der Editier-Bereich (EP_WID) ist kleiner als 10. Mehr dazu später.\\
Beispiel:\\
MOV EP_XPOS,4
MOV EP_YPOS,3
Der Bearbeitungsbereich beginnt in der fünften Spalte und der vierten Zeile. Wird ein Rahmen verwendet (EP_WIND = 0 oder 1), so ist dessen linke obere Ecke an dieser Position und die Bearbeitung beginnt in der fünften Zeile und der sechsten Spalte. Ist für die Eingabe ein Prompt angegeben (EP_TIT), so verschiebt sich der Eingabe-Bereich um die entsprechende Anzahl Spalten nach rechts. Mehr dazu später.\\
\\
\\
**EP_MODE** bestimmt, ob bei einer Eingabe (außer den Cursor-Tasten) die Zeile gelöscht wird oder nicht. Ist EP_MODE=2, so wird die Zeile bei einem Tastendruck nicht gelöscht, ist EP_MODE=0FFh, so wird die Zeile gelöscht, es sei denn, man betätigt zuerst die Cursor-Tasten.\\
Beispiel:\\
In den internen Anwendungen ist EP_MODE=2, wenn ein Dateiname (Speichern, Laden..) abgefragt wird. EP_MODE ist 0FFh, wenn eine Eingabe in einer Zelle der Tabellenkalkulation erfolgt.\\
Das Aktivieren des Rahmens ist beim Initialisieren von EP_XPOS, EP_YPOS und EP_WID zu berücksichtigen, der Cursor steht dann bei EP_XPOS+1 und EP_YPOS+1, und EP_WID sollte um 2 erhöht werden.\\
\\
\\
**EP_HIT** ist der Anfangs-Tastendruck der zu verarbeiten ist, bevor ein Benutzer-Tastendruck erfolgt. Wenn 0, dann findet keine Vor-Verarbeitung statt. FIXME\\
\\
\\
**EP_TIT** ist ein Doppelwort-Zeiger auf die Überschrift und Eingabeaufforderung des Eingabe-Bereichs, die verwendet werden, wenn der Rahmen eingeschaltet ist (EP_WIND = 0 oder 1).\\
Beispiel:\\
TIT DB "Titel",0,"Eingabe: ",0,0
.
.
.
MOV EP_TIT[0],OFFSET TIT
MOV EP_TIT[2],DS
Falls ein Rahmen (einfach oder doppelt) aktiviert ist, so steht in der oberen Leiste des Rahmens der Titel, und am linken Rand innerhalb des Rahmens die "Eingabe". Wenn eine Eingabe-Aufforderung angegeben ist, so ist dies beim Initialisieren von EP_YPOS und EP_WID von Bedeutung, der sichtbare Bereich wird um die Länge der Aufforderung kürzer.\\
\\
\\
**EP_EXIT** ist ein Doppelwort-Zeiger auf eine Liste von Tasten-Codes, die die Eingane-Routine beeenden. Diese Liste besteht aus 16Bit-Werten. Wenn das höherwertige Byte = 0 ist, so steht der ASCII-Wert im niederwertigen Byte. Wenn das höherwertige Byte nicht 0 ist, so handelt es sich um einen erweiterten Tastencode (z.B. 0148h für die linke Cursor-Taste).\\
Beispiel:\\
EXIT_KEYS DW 000Dh,001Bh,00h,00h
.
.
.
MOV EP_EXIT[0],OFFSET EXIT_KEYS
MOV EP_EXIT[2],DS
In diesem Fall führen nur die Eingabe-Taste und die Escape-Taste zur Beendung der Routine. Die Liste muss NULL-terminiert sein.\\
\\
\\
**EP_FN** ist ein Doppelwort-Zeiger auf die Routine der Tastatur-Abfrage. Im Beispiel-Programm heißt die Prozedur GET_KEY.\\
GET_KEY PROC
.
.
.
RETF
.
.
.
MOV AX,GET_KEY
MOV EP_FN[0],AX
MOV EP_FN[2],CS
Im Beispiel-Programm wird die Routine näher beschrieben.\\
\\
\\
**EP_WID** ist die Länge des gesamten Bearbeitungsbereichs wenn kein Rahmen gesetzt wurde. Wurde ein Rahmen gesetzt, so ist EP_WID die Breite des Fensters, der Bearbeitungsbereich ist um zwei Spalten kürzer. Ist eine Eingabeaufforderung angegeben, so ist der Bearbeitungsbereich um die Länge der Eingabeaufforderung kürzer.\\
Beispiel:\\
TIT DB "Titel",0,"Eingabe: ",0,0
STRG DB "Diese Zeile bearbeiten",0
.
.
.
MOV EP_TIT[0],OFFSET TIT ;Addresse der Titel-
MOV EP_TIT[2],DS ;Information
.
MOV EP_XPOS,2 ;Rahmen-Position. Zeile
MOV EP_YPOS,1 ;Rahmen-Position. Spalte
.
MOV EP_WID,20 ;Länge des Rahmens
MOV EP_WIND,1 ;doppelter Rahmen
Die linke obere Ecke des Rahmens wird sich in der zweiten Zeile und der dritten Spalte befinden. Der Rahmen wird eine Länge von insgesamt 20 Spalten haben (die rechte untere Ecke in der vierten Zeile und der dreiundzwanzigsten Spalte). Die Eingabeaufforderung "Eingabe: " beginnt in der dritten Zeile und der vierten Spalte, die zu bearbeitende Zeichenkette in der selben (dritten) Zeile, aber in der dreizehnten Spalte.\\
Somit bleiben nur 10 Spalten für die Darstellung der zu bearbeitenden Zeichekette (im Beispiel wird nur "Diese Zei" lesbar sein). Wärend der Bearbeitung wird der Inhalt der Zeichenkette innerhalb dieses 10-Spalten-Bereichs hin und her gescrollt.\\
Um einen möglichst großen Bereich der Zeichenkette anzuzeigen sollte EP_WID erhöht werden. Dabei ist darauf zu achten, dass der rechte Rand des Rahmens nicht über den Bildschirmbereich (Spalte 39) hinausragt. Ist dies nicht zu vermeiden, so muss der Bildschirm in den Systemeinstellungen auf statisch oder dynamisch umgestellt werden, sonst wird der Rahmen falsch dargestellt.\\
\\
\\
**EP_WIND** bestimmt ob ein Rahmen verwendet wird, und ob dieser einfach oder doppelt ist. Ist kein Rahmen erwünscht, so ist dieser Wert auf 0FFh zu setzen, für einen einfachen Rahmen ist der Wert 0, und für einen doppelten Rahmen der Wert 1 zu verwenden. Portfolios Anwendungen verwenden immer den doppelten Rahmen, dieser ändert sich beim Beenden der Bearbeitung automatisch zu einen einfachen Rahmen um anzuzeigen, dass die Bearbeitung nicht mehr aktiv ist.\\
Beispiel:\\
MOV EP_WIND,0FFh ;kein Rahmen
Wird ein Rahmen verwendet, so werden Überschrift und Eingabeaufforderung aus der angegebenen Adresse eingesetzt. Sind Überschrift oder Eingabeaufforderung nicht erwünscht, so sind sie als leer anzugeben.\\
Beispiel:\\
TIT DB "",0,"",0,0
.
.
.
MOV EP_TIT[0],OFFSET TIT ;Addresse der Titel-
MOV EP_TIT[2],DS ;Information
.
.
.
MOV EP_WIND,1 ;doppelter Rahmen
\\
\\
**EP_RES** ist für das BIOS reserviert. Ein Zugriff auf diesen Wert kann verhehrende Folgen haben. Schreiben sie auf keinen Fall in diesen Bereich!\\
\\
\\
**EP_UDEL** ist ein Doppelwort-Zeiger auf die Undelete-Routine. Diese muss dafür sorgen, dass gelöschte Zeichen zurückgeholt werden können. Wenn bei der Bearbeitung einer Zeichenkette Zeichen gelöscht werden (z.B. durch Entf. oder die Korrekturtaste), so müssen diese gespeichert werden um später wieder eingefügt werden können. Die internen Anwendungen verwenden dafür die Datei C:\SYSTEM\UNDELETE.DAT. Diese Datei hat eine bestimmte Struktur.\\
Sie besteht aus mehreren Datenblöcken. Jeder Block besteht aus einer Anzahl Zeichen die mit einem einzelnen Befehl gelöscht wurden. Jeder Block hat folgendes Format:\\
Daten Länge Richtung
Die Daten sind die gelöschten Zeichen (Zeilenumbrüche werden als 0Dh, nicht 0Dh 0Ah, gespeichert), Länge ist die Anzahl der Zeichen im Block und Richtung ist 0 wenn nach links gelöscht wurde (z.B. mit der Korrekturtaste) und 1 wenn nach rechts gelöscht wurde (z.B. mit Entf).\\
\\
FIXME\\
==== Beispielprogramm ====
\\
Folgendes Beispiel ist ein kleines Programm, dass die Funktionsweise des Zeileneditors veranschaulicht. Es wurde mit Erik Isaacsons Assembler A86 v3.22 auf dem Portfolio assembliert und getestet.\\
Durch ändern der Werte in der Prozedur INIT_EDLIN lässen sich Darstellung und Verhalten des Zeileneditors verändern.
\\
\\
;EDLIN.COM Zeileneditor der AES
;löscht den Bildschirm, führt Zeilen-Editor aus,
;gibt die bearbeitete Zeichenkette aus und
;wird beendet.
DATA_SEG SEGMENT
STRG DB "Diese Zeile bearbeiten.",0
EDBUF DB 78 DUP (?) ;Bearbeitungspuffer
EP_TARG DW 2 DUP (?) ;Adresse der Zeichenkette
EP_POS DW (?) ;Position innnerhalb der Kette
EP_MAX DW (?) ;max. Länge der Kette
EP_XPOS DB (?) ;Cursor-Position. Spalte.
EP_YPOS DB (?) ;Cursor-Position. Zeile.
EP_MODE DB (?) ;Zeichenkette löschen?
EP_HIT DW (?) ;Pre-Processing
EP_TIT DW 2 DUP (?) ;Adresse der Überschrift
EP_EXIT DW 2 DUP (?) ;Adresse der Liste für Ausstiegstasten
EP_FN DW 2 DUP (?) ;Adresse der Routine für Tastaturabfrage
EP_WID DB (?) ;max. Länge des Editier-Bereichs
EP_WIND DB (?) ;Rahmen-Typ (0FFh=keiner,0=einfach,1=doppelt)
EP_RES DW 2 DUP (?) ;reserviert! Nicht verändern.
EP_UDEL DW 2 DUP (?) ;Adressse der Undelete-Routine
DATA_SEG ENDS
CODE_SEG SEGMENT
MOV AH,0 ;AES initialisieren
INT 61h
JMP MAIN ;Sprung zur Haupt-Routine
;Prozeduren
INIT_EDLIN PROC ;Datenstruktur initialisieren
MOV AX,OFFSET EDBUF ;Adresse des Bearbeitungspuffers
MOV EP_TARG[0],AX
MOV EP_TARG[2],DS
MOV EP_POS,0 ;Position innnerhalb der Kette
MOV EP_MAX,80 ;max. Länge der Kette
MOV EP_XPOS,4 ;Position des Eingabebereichs (Rahmen). Spalte.
MOV EP_YPOS,2 ;Position des Eingabebereichs (Rahmen). Zeile.
MOV EP_MODE,2 ;Zeichenkette löschen? (2=nein, 0FFh=ja)
MOV EP_HIT,0 ;Pre-Processing (0=nein, 1=ja)
MOV AX,OFFSET TIT ;Adresse der Überschrift und Eingabeaufforderung
MOV EP_TIT[0],AX
MOV EP_TIT[2],CS
MOV AX,OFFSET EXK ;Adresse der Liste für Ausstiegstasten
MOV EP_EXIT[0],AX
MOV EP_EXIT[2],CS
MOV AX,GET_KEY ;Adresse der Routine für Tastaturabfrage
MOV EP_FN[0],AX
MOV EP_FN[2],CS
MOV EP_WID,31 ;maximale Länge des Bereichs oder der Rahmens
MOV EP_WIND,1 ;Rahmen-Typ (0FFh=keiner,0=einfach,1=doppelt)
MOV AX,UDEL ;Adressse der Undelete-Routine
MOV EP_UDEL[0],AX
MOV EP_UDEL[2],CS
RET ;Ende der Initialisierung der Datenstruktur
INIT_EDBUF PROC ;Zeichenkette in Puffer kopieren
MOV DI,0
MOV SI,0
IB0:
MOV AL,STRG[SI] ;Zeichen lesen
MOV EDBUF[DI],AL ;Zeichen speichern
CMP AL,0 ;Ende der Zeichenkette?
JE IB1
INC SI ;nächstes Zeichen
INC DI
JMP IB0 ;wiederholen
IB1: ;Ende der Zeichenkette
RET
GET_KEY PROC ;Tastaturabfrage
MOV AH,0 ;Taste abfragen
INT 16h ;Tastatut-Interrupt
CMP AL,0 ;Wenn Tastencode erweitert
JE GK0 ;dann nächstes Byte holen
MOV AH,0 ;Wenn kein Tastencode in AX
RETF ;dann Rückkehr
GK0:
MOV AL,AH ;Tastencode nach AL
MOV AH,1 ;AH = 1
RETF ;zurück
UDEL PROC ;Undelete-Routine
RETF ;zurück
ECHO_EDBUF PROC ;bearbeitete Zeichenkette ausgeben
MOV AH,2 ;Funktion Cursor-Position setzen
MOV BX,0 ;Page Nummer
MOV DH,5 ;Zeile
MOV DL,5 ;Spalte
INT 10h ;Bildschirm-Interrupt
MOV SI,0
EB0:
MOV DL,EDBUF[SI] ;Zeichen holen
CMP DL,0 ;Ende der Zeichenkette?
JE EB1 ;dann beenden
MOV AH,2 ;Zeichen ausgeben
INT 21h ;DOS-Interrupt
INC SI ;nächstes Zeichen
JMP EB0 ;wiederholen
EB1: ;beenden
RET
CLR_SCREEN PROC ;Bildschirm löschen
MOV AH,0 ;Bildschirm-Modus setzen
MOV AL,7 ;Text
INT 10h ;Bildschirm-Interrupt
RET
QUIT PROC ;Programm beenden
MOV AH,4Ch ;Funktion "beenden"
MOV AL,0 ;Fehlernummer
INT 21h ;zurück zu DOS
MAIN: ;Haupt-Routine
CALL CLR_SCREEN ;Bildschirm löschen
CALL INIT_EDBUF ;Zeichkette in Puffer lesen
CALL INIT_EDLIN ;Datenstruktur initialisieren
MOV SI,OFFSET EP_TARG ;Adresse der Datenstruktur
MOV AH,1 ;Funktion Zeileneditor
INT 60h ;AES-Interrupt
CALL ECHO_EDBUF ;Zeichenkette ausgeben
CALL QUIT ;beenden
END MAIN ;ende der Haupt-Routine
TIT DB "Titel",0,"Eingabe: ",0,0 ;Überschrift und Eingabeaufforderung
EXK DW 000Dh,001Bh,00h ;Liste der Ausstiegstasten (Esc und Eingabetaste)
CODE_SEG ENDS
\\
Einige Routinen des Programms könnten durchaus kleiner sein, hier wurde Wert auf die Nachvollziehbarkeit gelegt, der Quellcode hat didaktischen Character und kann durchaus optimiert werden.
Die Undelete-Routine fehlt noch.\\
\\
Wenn man ein wenig mit den Werten in der Prozedur INIT_EDLIN herumspielt merkt man sehr bald, daß es eine gewisse Abhängikgeit zwischen diesen gibt.\\
Damit man in beim Programmieren keine Überraschungen erlebt sollte man diese gegenseitige Abhängigkeit berücksichtigen und evtl. nur die wichtigsten zu definieren, die anderen vom Programm selbst daraus errechnen lassen.\\
\\
Wenn der Rahmen aktiviert ist (EP_WIND < 0FFh):\\
EP_XPOS < 40-EP_WID\\
EP_YPOS < 6\\
Bearbeitungsbereich = EP_WID - 2 - Länge der Eingabeaufforderung\\
rechter Rand des Rahmens ist bei EP_XPOS + EP_WID\\
\\
\\
Wenn der Rahmen deaktiviert ist (EP_WIND = 0FFh):\\
EP_XPOS < 40-EP_WID\\
EP_YPOS < 8\\
Bearbeitungsbereich = EP_WID\\
\\
\\
In dem obigen Beispiel fehlt noch eine wichtige Komponente, ohne die nocht nicht wirklich von AES die Rede sein kann. Das Speichern und wiederherstellen des Bildschirminhalts. Dies ist auch für die Verwendung von Menüs sehr wichtig, und wird deshalb auch in dem entsprechenden Abschnitt behandelt.\\
\\
===== Menüs =====
Menüs werden mit der Funktion 0Fh des Interrupt 60h aufgerufen. Nach erfolgter Auswahl gibt die Funktion die Nummer der gewählten Option zurück. Anders als beim Zeileneditor, werden die Werte für die Menüsteuerung in Registern abgelegt.\\
;MINIMENU.COM
MOV AH,15 ;Menü-Funktion
MOV BH,0 ;Video-Page
MOV AL,0 ;Menütiefe (0:keine Prüfung)
ADD AL,1 ;doppel-Rahmen
MOV CH,0 ;oberste Option
MOV CL,1 ;Cursor-Zeile
MOV DH,1 ;Menü-Position. Zeile.
MOV DL,5 ;Menü-Position. Spalte
MOV SI,OFFSET MENTXT ;Zeiger auf Menütext
MOV DI,0FFFFh ;kein Default-Text
INT 60h
MOV DX,AX ;Rückgabe sichern
MOV AL,0 ;ERRORLEVEL 0
CMP DX,-1 ;Esc gedrückt?
JE MM0 ;Abbruch
MOV AL,DL ;ERRORLEVEL =
ADD AL,DH ;DL+DH = Option
MM0:
MOV AH,4Ch ;Programm beenden
INT 21h ;zurück zu DOS
MENTXT DB "Titel",0,"Erstens",0,"Zweitens",0,"Drittens",0,"Viertens",0,0
Im ersten Block wird die Menüfunktion aufgerufen, im zweiten Block wird die Rückkgabe ausgewertet und das Ergebnis als ERRORLEVEL ausgegeben um in einer Batch-Datei ausgelesen zu werden (wenn 0, dann wurde die Escape-Taste gedrückt, wenn nicht, dann ist der ERRORLEVEL die gewählte Oprion).\\
Nach dem Start kann mann mit den Cursor-Tasten auf und ab scrollen und die Auswahl mit der Eingabe-Taste bestätigen, oder direkt den Anfangsbuchstaben der gewünschten Option tippen und das Programm wird beendet.\\
==== Die Register ====
**AH**: Funktionsnummer 15 (0Fh)
**AL**: Bit 1 signalisiert die Art des Rahmens (0 für einfach, 1 für doppelt). Bits 3 bis 7 sind die Menütiefe. Wenn mehr Menü-Optionen vorhanden sind als das Menü Zeilen hat (ohne den Rahmen zu zählen), so ist die gewünschte Menühöhe (inkl. Rahmen) mit acht zu multiplizieren (die Menüfunktion erwartet die Höhenangabe in Bits 3 bis 7). Wenn diese Bits gleich Null sind, so findet keine Tiefenprüfung satt, der Programmierer hat Sorge zu tragen, dass das Menu nicht über den unteren Bildschirmrand hinausragt. Ein Menu, das in der ersten Bildschirmzeile beginnt kann maximal 6 Optionen darstellen.\\
**BH**: die Video-Page (normalerweise 0).\\
**CX**: Bestimmt wo sich der Cursor zu Beginn befinden soll (meistens die Option die zuletzt ausgewählt wurde. CH bestimmt den Ausschnitt der Liste, das heißt, welche Option in der ersten Menüzeile erscheinen soll. CL bestimmt in welcher Zeile des Menüs der Cursor steht.\\
**DX**: Enthält die Bildschirm-Position des Menüs, DH die Zeile und DL die Spalte.\\
**SI**: Enthält die Adresse des Menütextes (Überschrift und Optionen).\\
Der Menutext betseht aus der Überschrift und aller Menü-Optionen, jeweils durch NULL getrennt und am Ende doppelt NULL-terminiert.\\
**ES:DI**: Enthält die Adresse des Default-Textes (jede Option kann rechts einen Parameter oder Zustand anzeigen).\\
Der Default-Text besteht aus durch NULL getrennte Zeichenketten (für jede Option eine), und ist doppelt NULL-terminiert.\\
Nach Ausführung der Menüfunktion enthält das Register **AX** das Ergebnis. Ist AX = -1, so hat der Anwender das Menü mit der Ecape-Taste verlassen. Wenn nicht, enthält AH die Optionsnummer die in der obersten Menüzeile stand (von Null an gezählt) und AL die Zeile die im Menü ausgewählt wurde. Die Ordinalzahl innerhalb der Liste erhält man durch die Summe beider Werte.\\
\\
==== Bildschirm wiederherstellen ====
Das obere Beispiel ist das simpelste. Im realen Einsatz wird man mehr als ein Menü verwenden wollen und das in verschiedenen Ebenen. Leider hat die Menüfunktion keine automatische Bildschirm-Wiederherstellung, so ist die Rückkehr in ein Übergeordnetes Menü nicht sehr schön. Die AES bieten aber Unterstützung um eine solche Wiederherstellung zu programmieren: Funktion 08h (Bildschirm speichern/laden) und Funktion 10h (Fläche berechnen) des Interrupt 60h.\\
\\
Für das Zwischenspeichern der Bildschirminhalte müssen Puffer angelegt werden. Der größte nötige Puffer ist 320 Bytes lang (der gesamte Bildschirm).\\
SCRBUF0 DB 320 DUP (?) ;Puffer für Menü-Ebene 0
SCRBUF1 DB 320 DUP (?) ;Puffer für Menü-Ebene 1
.
.
.
Funktion 10h (Fläche berechnen) des Interrupt 60h berechnet die Breite und Tiefe des angegebenen Menüs (inkl. Default-Texte) und liefert die Position der rechten unteren Ecke, ein Wert, der für die Funktion 08h (Bildschirm speichern/laden) benötigt wird.\\
.
.
.
MENTXT DB "Titel",0,"Erstens",0,"Zweitens",0,"Drittens",0,"Viertens",0,0
MENTOP DW 260 ;linke obere Ecke. Zeile, Spalte.
MENBUT DW (?) ;rechte untere Ecke. Zeile, Spalte.
MENITM DW (?) ;Anzahl Menü-Optionen.
MENSCR DW (?) ;Größe des benötigten Bildschirmpuffers.
;Fläche berechnen
MOV AH,10h ;Funktion Fläche berechnen
MOV DX,MENTOP ;MENTOP nach DX
MOV SI,OFFSET MENTXT ;Adresse des Menütextes
MOV DI,0FFFFh ;kein Default-Text
INT 60h ;AES-Interrupt
MOV MENITM,AX ;Anzahl Menü-Optionen
MOV MENBUT,CX ;untere rechte Ecke
MOV MENSCR,BX ;Anzahl Bytes (Größe des nötigen Puffers)
;Bildschirmbereich speichern
MOV AH,8 ;Funktion speichern/laden
MOV AL,0 ;speichern
MOV BH,0 ;Video-Page
MOV SI,SCRBUF0 ;Adresse des Puffers
MOV CX,MENBUT ;rechte untere Ecke
MOV DX,MENTOP ;linke obere Ecke
INT 60h ;AES-Interrupt
.
.
.
Nun ist der Inhalt des Bidschirmbereichs in dem angegebenen Puffer (SCRBUF0) gespeichert. Die Menüfunktion kann aufgerufen werden und der Bildschirmbereich kann wiederhegstellt werden.\\
.
.
.
;Bildschirmbereich laden
MOV AH,8 ;Funktion speichern/laden
MOV AL,2 ;laden
MOV BH,0 ;Video-Page
MOV SI,SCRBUF0 ;Adresse des Puffers
MOV CX,MENBUT ;rechte untere Ecke
MOV DX,MENTOP ;linke obere Ecke
INT 60h ;AES-Interrupt
Der ursprüngliche Inhalt des Bildschirmbereichs ist wiederhergestellt.\\
===== Meldungen =====
Die AES bieten zwei Arten von Meldungen mit unterschiedlichen Eigenschaften. Die Banachrichtigungen (wie z.B. beim Speichern und Laden von Dateien) und die Fehlermeldungen (wie z.B. Teilung durch NULL).
==== Benachrichtigung ====
Wird mit der Funktion 12h des Int 60h aufgerufen. Die Benachrichtigung zeigt eine Zeichenkette in doppeltem Rahmen mit Überschrift. Die Message-Funktion erwartet die Adresse des Textes in CS:SI.\\
Beispiel:\\
MOV AH,0 ;AES initialisieren
INT 61h
MOV SI,OFFSET MSG ;Adresse des Textes (CS:SI)
MOV BH,0 ;Page-Nummer
MOV DH,3 ;linke obere Ecke des Rahmens. Spalte.
MOV DL,2 ;linke obere Ecke des Rahmens. Zeile.
MOV AH,12h ;Funktion Message
INT 60h ;AES-Interrupt
MOV AH,4Ch ;Programm beenden
MOV AL,0 ;Fehlernummer
INT 21h
MSG DB "Nachricht",0,"Dies ist eine AES-Benachrichtigung.",0,0
Der Programmierer muss selber für die Wiederherstellung des Bildschirms sorgen. Siehe dazu den entsprechenden Abschnitt.\\
\\
==== Fehlermeldung ====
Wird mit der Funktion 14h des Int 60h aufgerufen. Die Fehlermeldung zeigt eine Zeichenkette in einem doppelten Rahmen (ohne Überschrift), gibt einen Warnton aus und wartet auf einen Tastendruck. Im Gegensatz zur Benachrichtigung sorgt die Funktion selbst für die Wiederherstellung des Bildschirms.\\
Beispiel:\\
MOV AH,0 ;AES initialisieren
INT 61h
MOV SI,OFFSET MSG ;Adresse des Textes (CS:SI)
MOV BH,0 ;Page-Nummer
MOV DH,10 ;linke obere Ecke des Rahmens. Spalte.
MOV DL,1 ;linke obere Ecke des Rahmens. Zeile.
MOV AH,14h ;Funktion Message
INT 60h ;AES-Interrupt
MOV AH,4Ch ;Programm beenden
MOV AL,0 ;Fehlernummer
INT 21h
MSG DB "Zeile 1",0,"Zeile 2",0,"Zeile 3",0,0
In diesem Beispiel besteht der Text aus drei, durch NULL getrennte Zeilen. Fehlermeldungen haben keine Überschrift.\\
\\
===== Wählton =====
Der Wählton wird mit der Funktion 17h des INT 61h aufgerufen, die Adresse der Wahlfolge wird in DS:SI erwartet.
Mit Hilfe der Funktion 18h kann auch die Länge des DTMF-Tons bestimmt werden um die Wählgeschwindigkeit zu beeinflussen.\\
Beispiel:\\
;AESDIAL.COM
TEL DB "0123456789ABCD*#",0,0 ;Wahlfolge
MOV AH,0 ;AES initialisieren
INT 61h
MOV AH,18h ;Funktion Mute-Sates
MOV AL,09 ;DTMF-Länge setzen
MOV DX,19000 ;DX zwischen 0 und 65535, 29411 typisch
INT 61h
MOV SI,OFFSET TEL ;Adresse der Wahlfolge
MOV CX,16 ;Länge der Wahlfolge
MOV AH,17h ;Funktion DTMF ausgeben
INT 61h
MOV AH,4Ch ;Programm beenden
MOV AL,0 ;Fehelernummer
INT 21h
AESDIAL.COM ist 47 Bytes groß. Man könnte sich die wichtigsten Telefonnummern als Programme ablegen und nach Namen in der DOS-Eingabeauforderung aufrufen.\\
\\
===== Melodie-Töne =====
Die Funktion zur Ausgabe von Melodytönen wird durch den Interrupt 61H Fn 16H aufgerufen.
Der Code für den zu erzeugenden Ton ist im Register DL anzugeben (siehe Tabelle) und die Länge in 10ms-Schritten im Register CX.\\
Die Funktion arbeitet monophon, es kann nur eine Note auf einmal ausgegeben werden, Zweiklang oder Dreiklang sind nicht möglich. Die Tonleiter umfasst leider nur knapp über 2 Oktaven (25 Noten). Die kürzeste spielbare Note ist 10ms lang.\\
\\
Im folgenden Beispiel werden die Ton-Codes und deren Dauer in der Variable MLDY gespeichert. Das Programm übergibt diese Werte nach und nach der Fn 16H bis die NULL am Ende der Variable das Ende der Kette signalisiert.\\
\\
;Die Hex-Zahlen stehen für die Ton-Codes, gefolgt von
;den Längen der Noten (mal 10ms) in Dezimal-Zahlen.
MLDY DB 39h,20,29h,20,3Ch,20,3Dh,20,3Eh,40,3Eh,40
DB 3Fh,20,3Fh,20,3Fh,20,3Fh,20,3Eh,40,80h,40
DB 3Fh,20,3Fh,20,3Fh,20,3Fh,20,3Eh,40,80h,40
DB 3Dh,20,3Dh,20,3Dh,20,3Dh,20,3Ch,40,3Ch,40
DB 3Eh,20,3Eh,20,3Eh,20,3Eh,20,39h,40,0
MOV AH,0 ;AES initialisieren
INT 61h
MOV CX,0 ;Länge zurücksetzen
MOV SI,0 ;Zeiger in Melodie-Folge
MM0:
MOV DL,MLDY[SI] ;Ton-Code nach DL
CMP DL,0 ;Ende der Melodie?
JE MM9 ;dann Ende
INC SI ;nächstes Byte
MOV CL,MLDY[SI] ;Längen-Code
MOV AH,16h ;Funktion Melodieton ausgeben
INT 61h ;AES-Interrupt
INC SI ;nächstes Byte
JMP MM0 ;Schleife
MM9:
MOV AH,4Ch ;Programm beenden
MOV AL,0 ;Fehlernummer
INT 21h ;DOS-Interrupt
==== Ton-Codes ====
Hier die Tabelle der Ton-Codes mit den entsprehenden Noten:\\
Ton-Code Note Hz
30H D#5 622,3
31H E5 659,3
32H F5 698,5
33H F#5 740
34H G5 784
35H G#5 830,6
36H A5 880
37H A#5 932,3
38H B5 987,8
39H C6 1046,5
3AH C#6 1108,7
29H D6 1174,7
3BH D#6 1244,5
3CH E6 1318,5
3DH F6 1396,9
0EH F#6 1480
3EH G6 1568
2CH G#6 1661,2
3FH A6 1760
04H A#6 1864,7
05H B6 1975,5
25H C7 2093
2FH C#7 2217,5
06H D7 2349,3
07H D#7 2489
Am Ende der zweiten und dritten Zeile der Melodie-Folge wird der Ton-Code 80h angegeben, er ist nicht dokumentiert, aber der "stummste" aller Ton-Codes (neben C0h). Weitere undokumentierte Ton-Codes weiter unten.
\\
==== Abspielgeschwindigkeit ====
Um die Abspielgeschwindigkeit zu ändern, müsste man in obigen Beispiel alle Längenangaben in der Variable MLDY austauschen. Um dies zu verhindern, sollte die tatsächliche Länge erst zur Laufzeit ermittelt werden, in der Variable stünden dann nur die Längenverhältnisse in Bezug auf eine Konstante. Hier erweist sich die Angabe in 10ms-Schritten als etwas ungünstig, wenn man eine hohe Zeitauflösung braucht (16tel, 32stel oder gar 64stel Noten). Die Zahlen in der Tabelle sind Längencodes, wie sie in CX erwartet werden, die Spalte ganz links enthält die resultierende Taktgeschwindigkeit in BPM (Viertel-Noten pro Minute).\\
BPM 1 1/2 1/4 1/8 1/16 1/32 1/64
375 64 32 16 8 4 2 1
187.5 128 64 32 16 8 4 2
125 192 96 48 24 12 6 3
93.75 256 128 64 32 16 8 4
75 320 160 80 40 20 10 5
62.5 348 192 96 48 24 12 6
53,5714 448 224 112 56 28 14 7
46,875 512 256 128 64 32 16 8
375 BPM ist rasend schnell, 187,5 BPM geht gerade noch. Um die 120 BPM haben wir aber nur 125 BPM und 93 BPM. Das sind recht extreme Geschwindigkeitsunterschiede. Kann man auf 64stel Noten verzichten, so wird es etwas besser:\\
BPM 1 1/2 1/4 1/8 1/16 1/32
250 96 48 24 12 6 3
187.5 128 64 32 16 8 4
150 160 80 40 20 10 5
125 192 96 48 24 12 6
107,1428 224 112 56 28 14 7
93,75 256 128 64 32 16 8
83,33 288 144 72 36 18 9
75 320 160 80 40 20 10
68,18 352 176 88 44 22 11
62.5 384 192 96 48 24 12
57,6923 416 208 104 52 26 13
53,5714 448 224 112 56 28 14
50 480 240 120 60 30 15
Man betrachte den Bereich um die 120 BPM: 125, 107, 94 ... Noch mehr Spielraum erreicht man nur, wenn man die 32stel Noten auch noch weg lässt:
BPM 1 1/2 1/4 1/8 1/16
250 96 48 24 12 6
214,2857 112 56 28 14 7
187.5 128 64 32 16 8
166.66 144 72 36 18 9
150 160 80 40 20 10
136.3636 176 88 44 22 11
125 192 96 48 24 12
115,3846 208 104 52 26 13
107,1428 224 112 56 28 14
100 240 120 60 30 15
93,75 256 128 64 32 16
88,2352 272 136 68 34 17
83,33 288 144 72 36 18
78,9473 304 152 76 38 19
75 320 160 80 40 20
71,4286 336 168 84 42 21
68,18 352 176 88 44 22
65,2174 368 184 92 46 23
62.5 384 192 96 48 24
60 400 200 100 50 25
57,6923 416 208 104 52 26
55.55 432 216 108 54 27
Nun hat man um die 120 BPM herum mehr Werte: 125, 115, 107, 100, und 94. Simple Melodien wie "Alle meine Entchen", die nicht einmal 16tel Noten beinhalten kann man sogar folgendermaßen aufteilen:
BPM 1 1/2 1/4 1/8
250 96 48 24 12
230,7692 104 52 26 13
214,2857 112 56 28 14
200 120 60 30 15
187.5 128 64 32 16
176,4706 136 68 34 17
166.66 144 72 36 18
157,8947 152 76 38 19
150 160 80 40 20
142,8571 168 84 42 21
136,3636 176 88 44 22
130,4348 184 92 46 23
125 192 96 48 24
120 200 100 50 25
115,3846 208 104 52 26
111.11 216 108 54 27
107,1428 224 112 56 28
103,4483 232 116 58 29
100 240 120 60 30
96,77 244 124 62 31
93,75 256 128 64 32
90.9090 264 132 66 33
88,2352 272 136 68 34
85,7143 280 140 70 35
83,33 288 144 72 36
81,081081 296 148 74 37
78,9473 304 152 76 38
76,9231 312 156 78 39
75 320 160 80 40
73,17073 328 164 82 41
71,4286 336 168 84 42
125, 120, 115, 111, 107, 103, 100, 97 und 94 BPM.\\
Beinhaltet eine Melodie Triplets, ist darauf zu achten, dass die Viertel-Noten einen Längencode enthalten der auch durch drei teilbar ist. Dies engt die Verfügbarkeit an Abspielgeschwindigkeiten wieder ein.\\
Vor dem Abspielen oder Programmieren einer Melodie, sollte man erst festlegen welche Auflösung man braucht (16tel, 32stel oder 64stel), dann alle Längenwerte als Vielfaches dieser kleinsten Einheit errechnen.\\
==== Undokumentierte Ton-Codes ====
\\
00H = Klick (dumpf)
01H = Klick
02H = Klick
03H = Klick
08H = F5 (32H) gedämpft
09H = G5 (34H) timbre
0AH = zwischen G#5 (35H) und A5 (36H), gedämpft
0BH = A#5 (37H) timbre
0CH = zwischen D6 (29H) und D#6 (3BH), timbre
0DH = E6 (3Ch) timbre
0FH = zwischen G6 (3Eh) und G#6 (2CH), timbre
10H DTMF
11H DTMF
12H DTMF
13H DTMF
14H DTMF
15H DTMF
16H DTMF
17H DTMF
18H DTMF
19H DTMF
1AH DTMF
1BH DTMF
1CH DTMF
1DH DTMF
1EH DTMF
1FH DTMF
20H Klick
21H Klick
22H Klick
23H Klick
24H zwischen D#6 (3Bh) und E6 (3Ch), timbre
26H zwischen D6 (29h) und D#6 (3Bh), timbre
27H zwischen C7 (25h) und C#7 (2Fh)
28H B5 (38H) timbre
2AH zwischen C6 (39h) und C#6 (3Ah)
2BH zwischen D#6 (3B) und E6 (3Ch)
2DH A#6 (04h) timbre
2EH zwischen B6 (05h) und C7 (25h)
40H Klick
41H Klick
42H Klick
43H Klick
44H A#6 (=04h)
45H B6 (=05h)
46H D7 (=06h)
47H D#7 (=07h)
48H F5 (=32h)
49H zwischen F#5 (33h) und G5 (34h)
4AH zwischen G#5 (35h) und A5 (36h)
4BH zwischen A#5 (37h) und B5 (38h) =0Ah ?
4CH zwischen D6 (29h) und D#6 (3Bh) =26h ?
4DH leicht unter E6 (3Ch)
4EH F#6 (=0Eh)
4FH leicht unter G#6 (2Ch)
50H leicht unter E6 (3Ch) =4Dh
51H dual tone ? ca 1200 Hz
52H dual tone ? ca 1330 Hz
53H dual tone ? hat 1480 Hz
54H dual tone ? ca 1200 Hz =51h ?
55H dual tone ? ca 1250 Hz
56H dual tone ? ca 1500 Hz
57H dual tone zwischen D6 (29h) und D#6 (3Bh) =26h ?
58H dual tone ? ca 1330 Hz
59H dual tone ? Tonhöhe wie 0Eh ?
5AH dual tone ? ca 1650 Hz
5BH dual tone ? ca 1650 Hz 5Ah ?
5CH dual tone ? ca 1650 Hz 5Ah ?
5DH dual tone ? ca 1650 Hz 5Ah ?
5EH dual tone zwischen D6 (29h) und D#6 (3Bh) =26h ?
5FH dual tone ? Tonhöhe wie 0Eh ?
60H Klick
61H Klick
62H Klick
63H Klick
64H zwischen D#6 (3Bh) und E6 (3Ch) =24h ?
65H C7 (=25h)
66H zwischen D6 (29h) und D#6 (3Bh) =26h ?
67H leicht unter C#7 (2FH)
68H leicht über B5 (38h)
69H D6 (=29h)
6AH zwischen C6 (39h) und C#6 (3Ah)
6BH zwischen D#6 (3Bh) E6 und (3Ch)
6CH G#6 (=2CH)
6DH leicht unter A#6 (04h)
6EH zwischen B6 (05h) und C7 (25h)
6FH C#7 (=2FH)
70H D#5 (=30h)
71H E5 (=31h)
72H F5 (=32h)
73H F#5 (=33h)
74H G5 (=34h)
75H G#5 (=35h)
76H A5 (=36h)
77H A#5 (=37h)
78H B5 (=38h)
79H C6 (=39h)
7AH C#6 (=49h)
7BH D#6 (=3B)
7CH E6 (=3Ch)
7DH F6 (=3D)
7EH G6 (=3EH)
7FH A6 (=3FH)
(80H...8FH = 40H..4FH)
80H stumm
81H Klick (leise)
82H Klick (leise)
83H Klick (leise)
84H A#6 wie (04h), aber lauter
85H B#6 wie (05h), aber lauter
86H D7 (=06h)
87H D#7 (=07h)
88H F5 (=32h)
89H zwischen F#5 (33h) und G5 (34h)
8AH zwischen G#5 (35h) und A5 (36h)
8BH zwischen A#5 (37h) und B5 (38h) =0Ah ?
8CH zwischen D6 (29h) und D#6 (3Bh) =26h ?
8DH leicht unter E6 (3Ch)
8EH F#6 (=0Eh)
8FH leicht unter G#6 (2Ch)
(90H...9FH = 10H...1FH)
90H DTMF (=10h)
91H DTMF (=11h)
92H DTMF (=12h)
93H DTMF (=13h)
94H DTMF (=14h)
95H DTMF (=15h)
96H DTMF (=16h)
97H DTMF (=17h)
98H DTMF (=18h)
99H DTMF (=19h)
9AH DTMF (=1Ah)
9BH DTMF (=1Bh)
9CH DTMF (=1Ch)
9DH DTMF (=1Dh)
9EH DTMF (=1EH)
9FH DTMF (=1FH)
A0H Klick (leise)
A1H Klick (hell)
A2H Klick
A3H Klick (lauter)
A4H Knattern
A5H Klick (hell, doppelt)
A6H Anschlag, pitch wie 26h (zwischen D6 (29h) und D#6 (3Bh))
A7H Anschlag, pitch zwischen C7 (25h) und C#7 (2Fh)
A8H Knattern wie A4h
A9H Klick3 ?
AAH Anschlag, pitch zwischen B5 (38h) und C5 (37h)
ABH Anschlag, pitch zwischen D#6 (3BH) und E6 (3CH)
ACH Anschlag, pitch wie G#6 (2Ch)
ADH Anschlag, pitch zwischen A6 (3FH) und A#6 (04H)
AEH Anschlag, pitch zwischen B6 (05h) und C7 (25h)
AFH C#7 (=25h)
B0H Knattern, wie A4h
B1H Klick, wie A0h
B2H Anschlag, F5 (32h)
B3H Anschlag, F#5 (33h)
B4H Anschlag, G5 (34h)
B5H Anschlag, G#5 (35h)
B6H Anschlag, A5 (36h)
B7H Anschlag, A#5 (37h)
B8H Anschlag, B5 (38h)
B9H Anschlag, C6 (39h)
BAH Anschlag, C#6 (3Ah)
BBH Anschlag, D#6 (3Bh)
BCH Anschlag, E6 (3Ch)
BDH Anschlag, F6 (3Dh)
BEH Anschlag, G#5 (3Eh)
BFH Anschlag, A6 (3Fh)
(C0H...CFH = 40H...4FH)
C0H Stille
C1H Klick, wie 01h
C2H Klick, wie C1h
C3H Klick, wie A0h
C4H A#6 (=04h)
C5H B6 (=05h)
C6H D7 (=06h)
C7H D#7 (=07h)
C8H F5 (=32h)
C9H zwischen F#5 (33h) und G5 (34h)
CAH zwischen G#5 (35h) und A5 (36h)
CBH zwischen A#5 (37h) und B5 (38h)
CCH leicht über D6 (29h)
CDH leicht über E6 (3Ch)
CEH F#6 (=0Eh)
CFH leicht unter G#6 (2Ch)
(D0H...DFH = 10H...1FH)
D0 DTMF
D1 DTMF
D2 DTMF
D3 DTMF
D4 DTMF
D5 DTMF
D6 DTMF
D7 DTMF
D8 DTMF
D9 DTMF
DA DTMF
DB DTMF
DC DTMF
DD DTMF
DE DTMF
DF DTMF
(E0H...EFH = A0H...AFH (außer Klicks))
E0H Klick (leise)
E1H Klick (hell)
E2H Klick
E3H Klick (lauter)
E4H Knattern
E5H Klick (hell, doppelt)
E6H Anschlag, pitch wie 26h (zwischen D6 (29h) und D#6 (3Bh))
E7H Anschlag, pitch zwischen C7 (25h) und C#7 (2Fh)
E8H Knattern wie A4h
E9H Klick3 ?
EAH Anschlag, pitch zwischen B5 (38h) und C5 (37h)
EBH Anschlag, pitch zwischen D#6 (3BH) und E6 (3CH)
ECH Anschlag, pitch wie G#6 (2Ch)
EDH Anschlag, pitch zwischen A6 (3FH) und A#6 (04H)
EEH Anschlag, pitch zwischen B6 (05h) und C7 (25h)
EFH C#7 (=25h)
(F0H...FFH = B0H...BFH)
F0H Knattern, wie A4h
F1H Klick, wie A0h
F2H Anschlag, F5 (32h)
F3H Anschlag, F#5 (33h)
F4H Anschlag, G5 (34h)
F5H Anschlag, G#5 (35h)
F6H Anschlag, A5 (36h)
F7H Anschlag, A#5 (37h)
F8H Anschlag, B5 (38h)
F9H Anschlag, C6 (39h)
FAH Anschlag, C#6 (3Ah)
FBH Anschlag, D#6 (3Bh)
FCH Anschlag, E6 (3Ch)
FDH Anschlag, F6 (3Dh)
FEH Anschlag, G#5 (3Eh)
FFH Anschlag, A6 (3Fh)
**Anmerkung:**
C0H...CFH = 40H...4FH
D0H...DFH = 10H...1FH
E0H...EFH = A0H...AFH (außer Klicks)
F0H...FFH = B0H...BFH