Joystick, Tastatur, Timer, Zufall, Lautsprecher und Zahlenanzeige an ihren Ports, und der Bildschirm, der das RAM als Pixel zeigt: Formate, Palette, Vsync und Seitenwechsel. Gilt auf allen drei Stufen der MiniCPU.
Geräte hängen an nummerierten Ports, getrennt vom RAM: Sie kosten keinen Speicher, auf jeder Stufe. IN liest ein Byte von einem Port in ein Register, OUT schreibt eins hin. Beide Befehle sind 3 Bytes lang und brauchen 4 Takte.
IN A, 0x02 ; A = Timer OUT 0x11, A ; A auf der Zahlenanzeige
| Port | Gerät | Das Byte | |
|---|---|---|---|
0x00 |
Joystick | IN |
die Tasten, die gerade gedrückt sind: Bit 0 ←, 1 →, 2 ↑, 3 ↓, 4 Leertaste |
0x01 |
Tastatur | IN |
das nächste getippte Zeichen (ASCII), oder 0, wenn nichts getippt wurde |
0x02 |
Timer | IN |
zählt 60-mal pro Sekunde Maschinenzeit hoch, 0–255, dann von vorn |
0x03 |
Zufall | IN |
bei jedem Lesen eine neue Zufallszahl 0–255 |
0x04 |
Vsync | IN |
wartet, bis der Bildschirm sein nächstes Bild aufnimmt, und gibt dann den Timer zurück |
0x05 |
Gehaltene Taste | IN |
wo die zuletzt gedrückte, noch gehaltene Taste liegt: Reihe × 16 + Spalte, +128 mit Shift; 0, wenn keine |
0x10 |
Lautsprecher | OUT |
spielt einen Ton; der Wert ist die MIDI-Notennummer (60 = c', 69 = a' mit 440 Hz) |
0x11 |
Zahlenanzeige | OUT |
zeigt den Wert als Dezimalzahl |
0x12 |
Bildschirmseite | OUT |
legt den Bildschirm an Adresse Wert × 256 (Seitenwechsel) |
0x13 |
Palettennummer | OUT |
wählt die Farbe, die Port 0x14 als Nächstes ändert |
0x14 |
Palettenfarbe | OUT |
Rot, Grün, Blau dieser Farbe (je 0–255); nach Blau ist die nächste Farbe dran |
0x15 |
Tonlänge | OUT |
wie lange die folgenden Töne klingen, in 1/60 s (ohne Angabe 6) |
Von einem Port zu lesen, an dem nichts hängt, oder auf ein Eingabegerät zu schreiben, meldet der Assembler als Fehler. In C heißen die Ports JOYSTICK, KEYBOARD, TIMER, RANDOM, VSYNC, KEYS, SPEAKER, NUMBER, SCREEN_PAGE, PALETTE, COLOR und NOTE_LENGTH: out(NUMBER, x), in(TIMER).
Sobald ein Programm einen Port benutzt, erscheint sein Gerät in der Leiste über dem Bildschirm, mit dem aktuellen Wert. Die Zahlenanzeige hat zusätzlich eine große Anzeige oben rechts am Bildschirm.

Ein Klick auf den Bildschirm schickt die Tasten an das Programm: Pfeiltasten (oder W A S D) und Leertaste an den Joystick, jedes getippte Zeichen außerdem an die Tastatur. Esc gibt die Tasten zurück. Liest ein Programm Joystick oder Tastatur, gibt »Run« dem Bildschirm die Tasten von selbst. Die Joystick-Knöpfe in der Leiste lassen sich auch anklicken.
0x00) meldet die Tasten, die gerade jetzt gedrückt sind, als Bits: 1 ←, 2 →, 4 ↑, 8 ↓, 16 Leertaste. In C: JOY_LEFT, JOY_RIGHT, JOY_UP, JOY_DOWN, JOY_FIRE, etwa if (in(JOYSTICK) & JOY_FIRE).0x01) sammelt getippte Zeichen in einer Queue, bis das Programm sie liest; jedes Lesen holt eins (als ASCII-Code), 0 heißt: nichts getippt.0x05) meldet, wo die zuletzt gedrückte, noch gehaltene Taste liegt, egal welches Tastaturlayout der Rechner hat: Reihe × 16 + Spalte. Shift (links oder rechts) zählt 0x80 dazu, Shift allein zählt als keine Taste. In C ist das Shift-Bit KEY_SHIFT.| Reihe | Tasten (deutsches Layout) | Codes |
|---|---|---|
| 1 | 1 2 3 4 5 6 7 8 9 0 ß ´ |
0x10–0x1B
|
| 2 | Q W E R T Z U I O P Ü + |
0x20–0x2B
|
| 3 | A S D F G H J K L Ö Ä # |
0x30–0x3B
|
| 4 | Y X C V B N M , . - |
0x40–0x49
|
| Shift | + 0x80
|
T ist also 0x24, Shift+T 0xA4. Das Beispiel 24-arpeggio-organ.c macht aus den drei Buchstabenreihen drei Oktaven.
Die CPU hat keine Interrupts: Ein Programm fragt seine Geräte immer wieder in einer Schleife ab.
16-breakout.asm.0x02) zählt Maschinenzeit: Takte geteilt durch den Takt. Ein Spiel, das auf den nächsten Timer-Schritt wartet, läuft bei jedem Takt gleich schnell, solange die CPU ihre Arbeit zwischen zwei Schritten schafft. Darunter wird es langsamer.0x04) wartet ohne Schleife: IN A, 0x04 lässt die CPU ruhen, bis der Bildschirm sein nächstes Bild aufnimmt, 60-mal pro Sekunde. Das ist der einfachste Takt für ein Spiel, und bei hohem Takt spart es dem Browser Millionen unnötiger Befehle. Mehr unter Vsync und Seitenwechsel.0x03) liefert bei jedem Lesen eine neue Zahl von 0 bis 255.Ein Schritt zurück macht ein OUT rückgängig (Zahlenanzeige, Bildschirmseite, Palette) und auch das Warten auf Vsync, aber keine gedrückten Tasten: Eingaben kommen von außerhalb der Maschine.
OUT 0x10, A spielt die Note A als MIDI-Notennummer: 60 ist das eingestrichene c, 69 das a mit 440 Hz, jede Zahl mehr einen Halbton höher. Wie lange sie klingt, bestimmt der letzte Wert an 0x15, in 1/60 Sekunden; ohne Angabe 6, also eine Zehntelsekunde.
LDI A, 12 OUT 0x15, A ; Töne von 0,2 s LDI A, 60 OUT 0x10, A ; c'
Der Ton beginnt in der Maschinenzeit des OUT, nicht erst, wenn der Browser das nächste Bild zeichnet. Eine Melodie hält ihren Rhythmus deshalb genau so genau, wie das Programm seine Zeit einteilt. Der Lautsprecher in der Geräteleiste hat einen Knopf zum Stummschalten.
Der Bildschirm ist eine Art, das RAM anzusehen: Wie ein Videochip liest er einen Block Bytes und zeichnet sie als Pixel, 60 Bilder pro Sekunde. Seine Einstellungen zu ändern, auch während ein Programm läuft, ändert kein Byte im RAM.
Ein Programm schlägt seinen Bildschirm mit einer Zeile vor, die keine Bytes erzeugt:
.display 0x80, 16, 8, gray8 .display screen, 80, 60, indexed4, pico_8
Die Reihenfolge ist .display start, breite, höhe, format [, palette], in C #pragma display 0x0800, 64, 48, mono. Ohne diese Zeile zeigt der Bildschirm 16 × 8 graue Pixel ab 0x80. Ein Bildschirm darf über dem eigenen Programm liegen; der Assembler warnt dann nur.

In der Sandbox stehen Format, Palette, Startadresse, Breite und Höhe in der Leiste über dem Bildschirm. Das Bild nach oben oder unten ziehen (oder das Mausrad) verschiebt das Fenster zeilenweise durchs RAM, der Griff in der Ecke ändert die Größe. Ein Klick auf ein Pixel wählt sein Byte in der RAM-Ansicht, die Maus über einem Pixel zeigt Koordinaten, Adresse und Wert. Der Knopf zum Zurücksetzen kehrt zur .display-Zeile zurück.
| Format | Pixel |
|---|---|
mono |
1 Bit pro Pixel, 2 Farben aus der Palette, 8 Pixel pro Byte |
indexed2 |
2 Bit pro Pixel, 4 Farben aus der Palette, 4 Pixel pro Byte |
indexed4 |
4 Bit pro Pixel, 16 Farben aus der Palette, 2 Pixel pro Byte |
indexed8 |
8 Bit pro Pixel, 256 Farben aus der Palette |
gray8 |
8 Bit pro Pixel als Grauton: 0 schwarz, 255 weiß |
rgb332 |
8 Bit pro Pixel: 3 Bit Rot, 3 Bit Grün, 2 Bit Blau |
rgb24 |
24 Bit pro Pixel: je ein Byte Rot, Grün, Blau |
Jede Zeile beginnt an einem neuen Byte: Eine Zeile braucht ⌈Breite × Bits pro Pixel / 8⌉ Bytes, der ganze Bildschirm Höhe mal so viele. Mehrere Pixel in einem Byte werden von den oberen Bits her gelesen:
| Byte | 1 Bit pro Pixel | 2 Bit | 4 Bit |
|---|---|---|---|
0x1B (00011011) |
0 0 0 1 1 0 1 1 |
0 1 2 3 |
1 11 |
Pixel (x, y) eines 16 Pixel breiten 8-Bit-Bildschirms ab 0x80 liegt also an 0x80 + 16·y + x, auf einem 64 Pixel breiten 1-Bit-Bildschirm im Byte start + 8·y + x/8, Bit 7 − x mod 8.
| Bildschirm | Pixel | Bytes |
|---|---|---|
| 32 × 16, 1 Bit | 512 | 64 |
| 16 × 16, 2 Bit | 256 | 64 |
| 16 × 8, 4 Bit | 128 | 64 |
| 8 × 8, 8 Bit | 64 | 64 |
| 80 × 60, 4 Bit (nur 8X) | 4800 | 2400 |
| 256 × 192, 4 Bit (nur 8X) | 49.152 | 24.576 |
In den Palettenformaten ist ein Pixel eine Farbnummer. Der Videochip schlägt die Farbe in seiner Palette nach: 256 Einträge mit Rot, Grün und Blau, je 0 bis 255. Die Palette liegt im Chip, nicht im RAM. Sie beginnt mit der Palette aus .display, sonst mit der ersten vordefinierten der passenden Größe. Ein Programm kann jede Farbe selbst setzen:
LDI A, 1 OUT 0x13, A ; Farbe 1 wird … LDI A, 255 OUT 0x14, A ; … Rot 255, LDI A, 128 OUT 0x14, A ; Grün 128, LDI A, 0 OUT 0x14, A ; Blau 0: orange
In C: out(PALETTE, 1); out(COLOR, 255); out(COLOR, 128); out(COLOR, 0);. Nach Blau geht der Chip von selbst zur nächsten Farbe weiter, so schreibt eine Schleife mit einem OUT 0x13 und vielen OUT 0x14 eine ganze Farbtabelle (14-mandelbrot.asm). So hat die VGA-Karte des PCs gearbeitet.
Eine geänderte Farbe ändert sofort jedes Pixel mit dieser Nummer, ohne ein Byte im RAM. Verschiebt ein Programm die Farben in jedem Bild, fließen stehende Bilder: Color Cycling, wie in 23-color-cycling.c. Mit wenigen Farben lassen sich auch viele vortäuschen: Dithering mischt zwei benachbarte Farben in einem feinen Muster (26-landscape.c).
Während ein Programm läuft, nimmt der Bildschirm zu Beginn jedes Bildes, 60-mal pro Sekunde Maschinenzeit, sein Bild von RAM und Palette auf; dazwischen zeigt er das letzte. IN A, 0x04 (in C in(VSYNC)) wartet auf diesen Moment:
for (;;) { in(VSYNC); // der Bildschirm hat gerade sein Bild aufgenommen change_colors(); // alle Änderungen erscheinen zusammen im nächsten move_things(); }
Was ein Programm gleich nach Vsync ändert, erscheint zusammen im nächsten Bild, nie halb fertig. Wer nicht wartet, kann mitten beim Zeichnen erwischt werden: Das alte Bild ist halb gelöscht, das neue halb gezeichnet. Dagegen hilft der Seitenwechsel: Das Programm zeichnet in eine zweite Seite, die gerade nicht zu sehen ist, und zeigt sie dann mit einem einzigen OUT 0x12, das den Bildschirm an Adresse Wert × 256 legt:
LDI A, 0x0C OUT 0x12, A ; der Bildschirm beginnt jetzt bei 0x0C00
In C: out(SCREEN_PAGE, address >> 8). Auf der 8 und 8S ist das RAM nur eine Seite groß, nützlich ist das also auf der 8X; 18-cube.c wartet vor jedem Wechsel auf Vsync. Angehalten oder im Einzelschritt zeigt der Bildschirm das RAM, wie es gerade ist.
Die erste Palette jeder Größe ist der Standard. Bis auf fire_256 stammen alle aus PixelRAM, in ihrer ursprünglichen Reihenfolge; fire_256 läuft von Schwarz über Rot, Orange und Gelb nach Weiß, für Hitzewerte (09-fire.asm, 21-life.c). Die Maus über einer Farbe zeigt ihre Nummer.
1bit_monitor_glow 2 Farben, Standard
cga_palette_1_high 4 Farben, Standard
color_graphics_adapter 16 Farben, Standard
pico_8 16 Farben
sweetie_16 16 Farben
vga 256 Farben, Standard
fire_256 256 Farben
Die Geräte und der Bildschirm gehören zur CPU Sandbox; diese Seite ist ihre Referenz. Dazu, auf Englisch:
.display, Palettenchip und Vsync, dazu, wie viele Pixel in den Speicher passen.08-input.asm, 09-fire.asm und 24-arpeggio-organ.c.