8-Bit-Breadboard-Computer auf Basis einer 6502-CPU - Assemblerprogrammierung Laufschrift auf 3 fach 7 Segment Anzeige
Bisherige Artikel dieser Serie - hier könnt ihr nochmal alle Grundlagen nachlesen, falls ihr jetzt erst einsteigt:- Digitale Logik und Logikgatter einfach erklärt
- Verwendung des 555-Timer als Taktgeber / Clock
- Das Clock-Modul: Taktgeber für unseren Breadboard-Computer
- Speichertypen und Zugriff auf Speicher
- Erste Schritte mit der CPU
- Eine echte WDC W65C02-CPU
- Das Speicher-Modul: Anbindung von RAM und ROM
- Erstes Programm in Maschinensprache: RAM-Test
- Das Sniffer-Modul: Ein Arduino/STM32 zeigt an, was auf dem Bus los ist.
- Erstes Ausgabegerät: Adressierung und Ausgabe auf 8 LEDs
- Programmiersprache-Evolution: von Maschinensprache zu Assembler
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 1: Taktungsprobleme
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 2: 20 Nanosekunden, die nicht sein sollten
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 3: Assembler-Programme
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 4: neue Adressierungsarchitektur mit 74HC688 und 74HC138
Heute geht es also wieder um die Programmierung des W65C02 mit Assembler.
Was soll das Programm können?
Wir haben nur drei 7-Segment-Anzeigen. Das reicht nicht gerade für viel Text. Aber man kann sich ja dem alten Prinzip der LAufschrift bedienen.Dabei laufen die Buchstaben von rechts nach links durch. Und in unserem Fall sind dann immer drei sichtbar. Eine Laufschrift mit nur drei Buchstaben ist zwar nicht supertoll zu lesen, aber da die meisten Silben auch nicht mehr als 3 Buchstaben haben, sollte das schon noch lesbar sein. Wir haben halt nicht mehr.
Hinzu kommt, dass eine 7-Segment-Anzeige eigentlich nur zur Anzeige von Ziffern gemacht ist und nicht von Text. Manchmal muss man bei der Darstellung der Buchstaben ein wenig kreativ seien - so dann später auch beim Interpretieren. Aber mit ein bisschen Kombinationsgabe und Übung hat auch der Leser den Dreh bald raus und kann Texte hierauf flüssig lesen.
Hello World
Hauptziel soll sein, den Text "Hello World" auszugeben. Das ist eine beliebte Übung und sie existiert eigentlich für jede Programmiersprache und jeden Computer. Die Aufgabe ist einfach: Der Computer soll mithilfe der verwendeten Programmiersprache eben den Text "Hello World" (oder auch "Hallo Welt") ausgeben. Anhand des Source-Codes, die diese Aufgabe erledigt, kann ein Programmierer dann abschätzen, wie aufwändig Programme in einer Programmiersprache zu schreiben sind.In einer Hochsprache wie Basic reicht zum Beispiel der Befehl
PRINT "HELLO WORLD"
In C würde der einfachste Source-Code
void main() {
printf ("Hello World");
}
lauten. In einer so verrückten (bzw. "esoterischen") Programmiersprache wie Brainfuck hieße der Source-Code
++++++++[->+++++++++<]>.<+++++[->+++++<]>++++.+++++++..+++.>+++++[->++++++<]>++.<+++++++[->+++++++<]>++++++.<+++++[->++++<]>++++.+++.------.--------.
Es gibt eigentlich für jede noch so exotische Programmiersprache ein Hello-World-Beispiel. Viele Beispiele finden sich auf Wikipedia.Aber wir befinden uns auf Assembler-Ebene (Beispiele dazu auf Wikipedia) und haben es nicht so einfach, einen hochsprachigen Befehl wie Print benutzen zu können. Und der 6502 hat einen extrem reduzierten Satz an OpCodes und nur wenige Register. Das macht es noch einmal aufwendiger. Noch dazu haben wir ein proprietäres Ausgabegerät, nämlich unsere Segmentanzeigen.
Ganz so trivial ist die Aufgabe mit unserem Breadboard-Computer also nicht.
1. Schritt: ASCII benutzen
Da wir auf dem PC entwickeln, benutzen wir dort den ASCII-Code. Das ist der Standard, welcher Bytecode (von 0 bis 255) welchem Zeichen zugeordnet ist. So entspricht z. B. die Null (0) dem ASCII-Code 48 (dez.), das große A etwa dem ASCII-Code 65 und das kleine a dem ASCII-Code 97. +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15
0- 15 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
16- 31 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
32- 47 SP ! " # $ % & ' ( ) * + , - . /
48- 63 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
64- 79 @ A B C D E F G H I J K L M N O
80- 95 P Q R S T U V W X Y Z [ \ ] ^ _
96-111 ` a b c d e f g h i j k l m n o
112-127 p q r s t u v w x y z { | } ~ DEL
Warum das so ist? Das hat eine lange Geschichte und geht auf das frühe Fernschreiber zurück, bei den man noch 7 Löcher in einen Paperstreifen gestanzt hat, um Daten zu speichern. Der Ursprungs-ASCII-Code hat darum auch nur 27, gleich 128 Zeichen. Aus historischen Gründen sind die ersten 32 Positionen auch mit nicht druckbaren Steuerzeichen belegt. So gibt es etwa auf Position 7 den Steuercode für die Glocke in einem Fernschreiber. Wurde dieser Code 7 übermittelt, dann klingelte das Glöckchen im Fernschreiber, z. B. um anzuzeigen, dass das Fernschreiben jetzt komplett übertragen ist.Fun Fact: Öffnet doch einmal eine Eingabeaufforderun und gebt dort
echo ^G^G^G
ein (die ^G nicht direkt so eingeben, sondern dazu STRG+G drücken) und drückt Return. Es erklingt dreimal ein Piepton. Wie kommt das? Nun, das Piepen steht für die Glocke und G ist der 7. Buchstabe im Alphabet. STRG steht übrigens für Steuerung (auf englisch CTRL = Control). STRG-A bedeutet also: das erste Steuerzeichen und STRG-G ist eben das 7. Steuerzeichen und das ist die Glocke. Ist doch witzig, wie es das Glockenzeichen vom Fernschreiber auf den modernen Windows-PC geschafft hat. Eigentlich benutzt das niemand mehr, es ist aus Kompatibilitätsgründen aber immer noch vorhanden.Moderne Computer rechnen mit 8 Bits das Byte und so wurden auf die 128 oberen Positionen besetzt. Das hat man für jedes Land bzw. Tastatur anders gemacht. Die Deutschen haben hier ihre Umlaute platziert, die Polen oder Tschechen ihre vielen "Buchstaben-Dekorationen" und so hat jedes Land irgendwie seine eigene Definition, Codepage genannt. Die wird gewechselt, wenn man die Ländereinstellung bzw. die Tastaturbelegung im Bestriebssystem ändert. Den Aufwand betreiben wir nicht und lassen die oberen 128 Bit einfach leer, sprich wir schreieben dort $FF für "alle Segmente aus" hinein.
Klar ist: unsere alten Definition der Segment-Muster
SEG:
BYTE $88, $BE, $C4, $94, $B2, $91, $81, $BC, $80, $90 ; 0 bis 9 = Ziffern 0 bis 9
BYTE $A0, $83, $C9, $86, $C1, $E1, $89, $A2, $BE, $9E ; 11 bis 20 = Buchst. A bis J
BYTE $A1, $CB, $A5, $A8, $88, $E0, $B0, $E9, $91, $BC ; 21 bis 30 = Buchst. K bis T
BYTE $8A, $DA, $D2, $D5, $92, $C4 ; 31 bis 36 = Buchst. U bis Z
können wir nicht mehr verwenden, wenn wir ASCII-Definitionen mit dem Definitionsbefehl
HELLO:
ASCII HELLO WORLD
benutzen wollen. Sicher könnten wir uns jetzt die Mühe machen, unseren Text als
HELLO:
BYTE 18, 15, ... ; 18 für H, 15 für E etc.
zu definieren. Aber Texte werden wir noch viele benutzen. Darum ist es einfacher eine zweite SEG-Tabelle zu erstellen, die wir SEG_ASC nennen wollen, und die die Muster bereits an der richtigen ASCII-Position hat:
SEG_ASC:
; ersten 32 Bytes leer, weil Steuerzeichen
BLKB 32, $FF
; SPC ! " # $ % & ' ( ) * + , - . /
BYTE $FF, $3E, $FA, $AA, $91, $E6, $C0, $FE, $C9, $9C, $B6, $E3, $BF, $F7, $7F, $E6
; 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
BYTE $88, $BE, $C4, $94, $B2, $91, $81, $BC, $80, $90, $3F, $3F, $C7, $D7, $97, $44
; @ A B C D E F G H I J K L M N O
BYTE $84, $A0, $83, $C9, $86, $C1, $E1, $89, $A2, $BE, $9E,$A1, $CB, $A5, $A8, $88
; P Q R S T U V W X Y Z [ \ ] ^ _
BYTE $E0, $B0, $E9, $91, $BC, $8A, $DA, $D2, $D5, $92, $C4, $C9, $B3, $9C, $F8, $DF
; ` a b c d e f g h i j k l m n o
BYTE $FB, $A0, $83, $C9, $86, $C1, $E1, $89, $A2, $BE, $9E,$A1, $CB, $A5, $A8, $88
; p q r s t u v w x y z { | } ~ DEL
BYTE $E0, $B0, $E9, $91, $BC, $8A, $DA, $D2, $D5, $92, $C4, $C9, $B3, $9C, $F8, $FF
; ASCII 128-255 = 128 Bytes leer
BLKB 128, $FF
BLKB ist ein neuer Befehl und wird wohl für "Block of Bytes" stehen. Es gibt übrigens auch BLKW für Block of Words, aber hier geht es ja um Bytes. Mit BLKB 32, $FF sparen wir uns, 32 mal hintereinander $FF zu schreiben. Das macht der Assembler für uns. Das Ergebnis ist das selbe: 32 $FFs hintereinander.Mit meinem selbstprogrammierten Segment-Rechner, den ich euch ja schon vorgestellt habe, habe ich die Zeichen, die wir noch nicht hatten, flugs noch "gemalt" und natürlich die bereits vorhandenen Definitionen (Ziffern, Buchstaben) an die richtigen Positionen kopiert.
Definieren wir jetzt also einen Text mit
ASCII HELLO WORLD
so übersetzt der Assembler das anhand der ASCII-Tabelle intern zu einem
BYTE 72, 69, 76, 76, 79, 32, 87, 79, 82, 76, 68
und wir haben jetzt mit unseren neuen Tabelle SEG_ASCI an Position 72 eben das erforderliche H, an 69 das E usw. usf.Das heißt, wir können mit SEG_ASC direkt das richtige Muster für einen ASCII-Text holen und dann auf der Segment-Anzeige ausgeben.
2. Schritt: Anzeigen
Der Anfang ist einfach: Das erste Zeichen des ASCII-Textes gehört auf die linke Anzeige, das 2. Zeichen auf die mittlere und das 3. Zeichen auf die rechte.ldy HELLO+0 ; holt das 1. Zeichen, also das H (bzw. 72) in das Y-Regsiter
lda SEG_ASC,y ; holt die 72. (steht so in Y) Muster-Definition in den Akkumulator
sta SEGL ; und schreibt das Muster auf das linke Segment
ldy HELLO+1 ; holt das 2. Zeichen, also das E (bzw. 69) in das Y-Regsiter
lda SEG_ASC,y ; holt die 69. (steht so in Y) Muster-Definition in den Akkumulator
sta SEGM ; und schreibt das Muster auf das mittlere Segment
ldy HELLO+2 ; holt das 3. Zeichen, also das L (bzw. 76) in das Y-Regsiter
lda SEG_ASC,y ; holt die 76. (steht so in Y) Muster-Definition in den Akkumulator
sta SEGL ; und schreibt das Muster auf das rechte Segment
Damit sind die ersten drei Zeichen HEL geschrieben. Im nächsten Schritt soll das H nach links rausscrollen, das E an die 1. Position, das L an die zweite Position und das neue L aus HELL an die dritte Position geschrieben werden, so dass dann ELL stehenbleibt. Angezeigt werden also immer drei Zeichen gleichzeitig, nur die Position, ab der der Text angezeigt wird, verschiebt sich nach rechts bzw. erhöht jeweils um eins:
HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD
HELLO WORLD
Im Grunde heißt das: immer drei Zeichen anzeigen ab der aktuelen Position, die wir von 0 bis Textlänge minus 1 hochzählen. Um das zu bewerkstelligen können wir noch einen weiteren Index benutzen, X haben wir ja noch übrig. Statt ldy HELLO+0/+1/+2 schreiben wir einfach ldy HELLO,x und erhöhen das X-Regsiter mit INX nach jeder Runde. Wie der binäre OpCode für INX ist, dass ist uns inzwischen egal, dass soll mal schön der Assembler für uns raussuchen. Wichtig nur zu wissen, dass es den Befehl gibt und wie seine dreistellige Abkürzung ist.
Apropos Assembler. Leider habe ich nicht herausfinden können, wie die Assembler-Anweisung lautet, um die Länge eines mit ASCII definierten Textes abzurufen. Das hätte ich eigentlich vom Assembler erwartet. Aber wahrscheinlich gibt es diesen Befehl einfach nicht. Also müssen wir HELLO WORLD selbst abzählen und kommen auf 11 Zeichen. Mit dem CPX-Befehl (ComPare X) können wir schauen, ob das X-Register einen bestimmten Wert hat, hier 11. Ist dies der Fall, weil wir am Textende angekommen sind, dann springen wir aus der Schleife heraus.
start:
ldx #0
print:
ldy HELLO,x ; X. Buchstaben aus "HELLO WORLD" in Y-Register laden
lda SEG_ASC,y ; Y. Zeichen aus 7-Segment-Muster-Tabelle in Akku laden
sta SEGL
ldy HELLO+1,x ; X.+1 Buchstaben aus "HELLO WORLD" in Y-Register laden
lda SEG_ASC,y ; Y. Zeichen aus 7-Segment-Muster-Tabelle in Akku laden
sta SEGM
ldy HELLO+2,x ; X.+2 Buchstaben aus "HELLO WORLD" in Y-Register laden
lda SEG_ASC,y ; Y. Zeichen aus 7-Segment-Muster-Tabelle in Akku laden
sta SEGR
inx
cpx #11 ; HELLO WORLD hat 11 Zeichen
bne fertig
bra print
fertig:
bra start ; noch eine Runde
Wir testen das Programm (siehe Video am Ende). Mir gefällt noch nicht, dass das HEL sofort da steht. Das soll auch hereinscrollen. Einfachste Lösung: zwei Leerzeichen davor setzen. Da Leerzeichen vor einem mit ASCII definierten Text aber als kosmetische Zeichen zur Einrückung gewertet sind, erledigen wir das mit einem BYTE 32,32 davor.3. Test und Optimierung des Programmes
Außerdem möchte ich mir das Abzählen der Zeichen des Textes sparen. Das müsste ich jedesmal machen, wenn ich den Text ändere. Einfacher wäre es doch, ein nicht verwendetes Zeichen - wir nehmen hier mal das Zeichen an Position 0 - zu "missbrauchen", um anzuzeigen, dass das Ende des Strings (Textes) erreicht ist. Nicht einfacher als das: Sezten wir ein BYTE 0 hinter die ASCII-Definition.Anstatt mit CPX #nn zu überprüfen, ob wir nn Durchläufen gemacht haben, prüfen wir einfach, ob das mit ldy HELLO,x in das Y-Register geladene Zeiche eine 0 ist. Und springen dann mit beq fertig aus der Print-Schleife heraus. Zur Erinnerung: BEQ steht für "Branch if EQual zero", also "Verzweige, wenn im Statusregsiter das Zero-Bit gesetzt ist". Und das Zero-Bit wird gesetzt, wenn wir eine 0 ins Y-Register laden. Wichtig ist halt, dass wir den BEQ-Befehl direkt nach dem LDY benutzen, nicht dass ein anderer Befehl das Zero-Bit wieder beeinflusst.
Beim Testen fällt mir dann auf, dass das Scrolling nicht ganz flüssig aussieht. Es ist zwar so schnell als nur irgend geht, aber trotzdem nicht schön, weil die alten Buchstaben noch kurz stehenbleiben (siehe Video am Ende).
Man müsste das Aufbauen der jeweiligen drei Buchstaben so schnell wie möglich machen, aber dann jeweils eine kurze Zeit warten, damit das Ergebnis länger stehen bleibt. Bei höheren Taktgeschwindigkeiten sieht man dann nur noch den richtigen Text und die Laufschrift sieht sauberer aus.
Unterprogramme (Subroutinen) und der Stack
So eine Warte-Funktion braucht man sicher öfters. Darum will ich ein Unterprogramm daruas machen. Unterprogramme haben auch eine Sprungmarke (Label) als Start-Kennzeichnung und ein RTS (ReTurn from Sub routine) am Ende. Beim Aufruf mit JSR (Jump Sub Routine) passiert folgendes:- JSR packt die Adresse des nächsten auszuführenden Befehles (also den Programmcounter +x) auf den Stack.
- Die CPU springt an die beim JSR angegeben Adresse (setzt den Programmcounter (PC) darauf) und führt hier stur alle weiteren Befehle dort aus.
- Trifft die CPU auf ein RTS, dann holt sie sich die oberste Adresse vom Stack und springt dorthin (setzt den PC darauf) und löscht die Rücksprungadresse vom Stack.
- Die CPU macht stur beim PC weiter, der jetzt die Rücksprungadresse ist. Dadurch wird das Programm an der Stelle vor dem Unterprogrammaufruf weiter ausgeführt.
An die erste Karte kommen wir jetzt nicht mehr so einfach dran. Das macht aber gar nichts, weil die CPU nach dem LIFO (Last In, First Out, logischerweise das Gleiche wie FILO First in, Last out) Prinzip arbeitet: Was zuletzt auf den Stapel gelegt wurde, wird als erstes wieder verwendet. Das bedeutet: wenn jetzt ein RTS kommt, holt sich die CPU die oberste Karte vom Stapel, setzt den Program Counter drauf und wirft die Karte weg (technisch: ändert die Adresse des Stackcounters).
Damit liegt beim ersten Rücksprung aus dem Unterunterprogramm nur noch eine Karte auf dem Stack. Und beim nächsten RTS wird diese letzte Karte benutzt. Und magischerweise (naja, eigentlich logischerweise) steht auf der Karte wieder die richtige Rücksprungadresse. Wieder wird die Karte weggeworfen und der PC angepasst. Der Kartenstapel ist jetzt leer. Würde die CPU jetzt dennoch wieder auf einen RTS treffen, dann weiß sie nicht mehr wohin. Es geschieht ein Stack Error und die CPU hält an oder startet neu. Es muss ein Programmierfehler vorliegen.
Mit PHA (PusH Accu), PHX (PusH X) und PHY (PusH y) kann man übrigens selbst etwas auf den Stack packen (pushen) und mit PLA (PulL Accu), PLX (PulL X) und PLY (PulL Y) vom Stack holen (pullen).
Würde unsere Subroutine z. B. über mehrere Schleifen zählen und dafür auch X und Y-Register einbeziehen, würde unser Delay diese Register verändern. Dann müsste sie Routine zu Beginn diese Register mit PHX und PHY auf dem Stack sichern und am Ende mit PLX und PLY wiederherstelle, damit für das aufrufende Programm alles beim Alten ist. Immer dran denken: alle Routinen und Programmteile benutzen die selben Register.
Lange Rede, kurzer Sinn: wir packen unsere Warteroutine einfach in eine Subroutine names delay. Die soll den Akku-Wert auswerten und von dort aus hinunterzählen. Und wenn sie bei Null angekommen ist, springt sie zurück. Der Akku-Wert ist also unser Aufrufparameter und bestimmt, wie lange gewartet werden soll (das ist natürlich wieder von der Taktgeschwindigkeit abhängig, wir haben ja keine Uhr im 6502).
Mit den Verbesserungen sieht der fertige Source-Code wie folgt aus. Und auch das Ergebnis sieht jetzt gut aus (siehe Video am Ende).
Fertiger Source-Code
; Hello World auf 3-fach Sieben-Segment-Anzeige ausgeben
; last edit: Oliver Kuhlemann (www.cool-web.de), 2020-07-10
CHIP W65C02S ; (default)
ORG $8000 ; Programmstartadresse (von der CPU aus gesehen)
; --- Konstanten: IO-Adressen ---
LED: equ $7000 ; 8-LED-Ausgabe, hört aber auf alles von $7000 bis $7fff
SEGL: equ $7001 ; linke 7-Segment-Anzeige
SEGM: equ $7002 ; mittlere 7-Segment-Anzeige
SEGR: equ $7003 ; rechte 7-Segment-Anzeige
init:
lda #0
sta LED ; LED-Ausgabe löschen
start:
ldx #0
print:
ldy HELLO,x ; X. Buchstaben aus "HELLO WORLD" in Y-Register laden
beq fertig
lda SEG_ASC,y ; Y. Zeichen aus 7-Segment-Muster-Tabelle in Akku laden
sta SEGL
ldy HELLO+1,x ; X.+1 Buchstaben aus "HELLO WORLD" in Y-Register laden
lda SEG_ASC,y ; Y. Zeichen aus 7-Segment-Muster-Tabelle in Akku laden
sta SEGM
ldy HELLO+2,x ; X.+2 Buchstaben aus "HELLO WORLD" in Y-Register laden
lda SEG_ASC,y ; Y. Zeichen aus 7-Segment-Muster-Tabelle in Akku laden
sta SEGR
inx
lda #10
jsr delay
bra print
fertig:
bra start
stop:
bra stop
; --- Unterprogramme ---
delay:
NOP ; Zeit verschwenden
NOP
dec A
bne delay
rts
; --- im ROM abgelegte Konstanten
HELLO:
BYTE 32, 32
ASCII HELLO WORLD
BYTE 0
; Definitionen für 7-Segment-Anzeige 5611 B (common Anode)
;
; 2
; __
; 4 | | 1
; -- 8
; 10 |__| 40 .=80
;
; 20
; 0...9 Ziffern, 11...36 Buchstaben
SEG:
BYTE $88, $BE, $C4, $94, $B2, $91, $81, $BC, $80, $90 ; 0 bis 9 = Ziffern 0 bis 9
BYTE $A0, $83, $C9, $86, $C1, $E1, $89, $A2, $BE, $9E ; 11 bis 20 = Buchst. A bis J
BYTE $A1, $CB, $A5, $A8, $88, $E0, $B0, $E9, $91, $BC ; 21 bis 30 = Buchst. K bis T
BYTE $8A, $DA, $D2, $D5, $92, $C4 ; 31 bis 36 = Buchst. U bis Z
; Abbildung der wichtigsten ASCII-Zeichen an der ASCII-Position
SEG_ASC:
; ersten 32 Bytes leer, weil Steuerzeichen
BLKB 32, $FF
; SPC ! " # $ % & ' ( ) * + , - . /
BYTE $FF, $3E, $FA, $AA, $91, $E6, $C0, $FE, $C9, $9C, $B6, $E3, $BF, $F7, $7F, $E6
; 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
BYTE $88, $BE, $C4, $94, $B2, $91, $81, $BC, $80, $90, $3F, $3F, $C7, $D7, $97, $44
; @ A B C D E F G H I J K L M N O
BYTE $84, $A0, $83, $C9, $86, $C1, $E1, $89, $A2, $BE, $9E,$A1, $CB, $A5, $A8, $88
; P Q R S T U V W X Y Z [ \ ] ^ _
BYTE $E0, $B0, $E9, $91, $BC, $8A, $DA, $D2, $D5, $92, $C4, $C9, $B3, $9C, $F8, $DF
; ` a b c d e f g h i j k l m n o
BYTE $FB, $A0, $83, $C9, $86, $C1, $E1, $89, $A2, $BE, $9E,$A1, $CB, $A5, $A8, $88
; p q r s t u v w x y z { | } ~ DEL
BYTE $E0, $B0, $E9, $91, $BC, $8A, $DA, $D2, $D5, $92, $C4, $C9, $B3, $9C, $F8, $FF
; ASCII 128-255 = 128 Bytes leer
BLKB 128, $FF
Video
Natürlich gibt es auch heute wieder ein Video, in dem ich abwechselnd die Programmierung in der Entwicklungsumgebung und den Test auf dem Breadboard-Computer zeige. Viel Spaß beim Anschauen.