Im abschließenden Teil dieser Serie habe ich mir vorgenommen, eine kleine Beispielanwendung in MOS 6502 Assembler zu schreiben. Der PC soll über das installierte, serielle Kabel gegenüber dem C64/C128 wie ein Client auftreten, der Zeichen an den Server (den Commodore Computer) sendet. Das Programm dient mehr als Demo und weniger als vollständig ausgetestete Software. Ich habe die einzelnen Code-Abschnitte gut dokumentiert, so dass man hoffentlich schnell eigene Experimente machen kann. Der Assembler-Code wurde im Turbo Macro Pro (TMP) erstellt, kann aber sicher leicht auf andere Assembler übertragen werden.
Das Programm liegt ausführbar in Maschinensprache ab Adresse $1000 (4096) und kann aus Basic heraus mit dem Befehl „SYS 4096“ gestartet werden:
*= $1000
; open rs-232 serial channel and read
; in bytes until x pressed
Am Anfang werden alle Einsprungadressen für Systemaufrufe definiert. Dafür werden fast durchgängig die empfohlenen, fixen Adressen der ROM Sprungtabelle („jump table“) verwendet:
; define kernel system calls
chkin = $ffc6; set input channel
chkout = $ffc9; set output channel
close = $ffc3; close logical file
clrchn = $ffcc; set default i/o dev
chrin = $ffcf; get chr from input dev
chrout = $ffd2; print accu to output
getin = $ffe4; get chr from input dev
linprt = $bdcd; print number
open = $ffc0; open logical device
readst = $ffb7; read i/o status
setnam = $ffbd; set filename params
setlfs = $ffba; set log file nr…
strout = $ab1e; print string
Jetzt folgt ein Schalter, ob man gewisse Debug-Informationen ausgeben lassen möchte. Steht der Schalter auf „1“ und nicht auf „0“, dann wird mit jedem Drücken der „D“-Taste ein kurzer Textblock ausgegeben, der später erklärt wird.
debug = 1
Anschließend wird der Zeichensatz fest auf „lower/uppercase“ eingestellt, Bildschirmfarbe und -rahmen festgelegt, der Bildschirm gelöscht sowie eine Willkommensbotschaft am Bildschirm ausgegeben.
; lower/uppercase chr set
lda $d018
ora #%00000010
sta $d018
; set up screen
lda #6 ; blue
sta $d021 ; background color
sta $d020 ; border color
lda #147 ; clear screen
jsr chrout
lda #<welcome
ldy #>welcome
jsr strout
Nun wird die RS-232 Schnittstelle parametrisiert und anschließend geöffnet.
; 6551 control register
; set 8n1, 300 baud
lda #%00000110
sta $0293
; 6551 command register
; set no parity, half-duplex, 3 line
lda #%00010000
sta $0294
; set logical file number
lda #$02 ; log file nr
ldx #$02 ; dev nr
ldy #$00 ; sec address
jsr setlfs
; set filename parameters
lda #$00 ; no filename
jsr setnam
; open rs-232 channel
jsr open
Hier folgt jetzt die Hauptschleife. In dieser wird ein Byte von der RS-232 Schnittstelle gelesen (besser gesagt vom Puffer). Danach wird exemplarisch Bit 2 (%0100 = 4) des Status-Registers auf einen Receiver Buffer Overrun getestet. Sollte dieser vorliegen, wird in eine Fehlerroutine verzweigt. Anschließend wird ein Zeichen aus dem Tastaturpuffer in den Akkumulator eingelesen. Wurde vom Anwender die „X“-Taste gedrückt, wird das Programm beendet. Hat der Anwender die „D“-Taste gedrückt, wird am Bildschirm des C64/C128 eine Debug-Meldung ausgegeben.
loop
; get byte from rs-232 channel
ldx #$02 ; rs-232
jsr chkin
jsr getin
jsr chrout
jsr clrchn
; test status reg for buffer overrun
lda $0297
and #4
bne error
; get a character from keyboard
jsr getin
cmp #68 ; d key pressed?
beq sdebug
cmp #88 ; x key pressed?
bne loop
sclose
; close rs-232 channel
lda #$02
jsr close
rts ; return to basic
Die sehr rudimentäre Fehlerbehandlung folgt:
error
lda #<errormsg
ldy #>errormsg
jsr strout
jmp sclose ; end program
Gefolgt vom Code für die Debug-Meldung:
sdebug
lda #13
jsr chrout
lda #<d1
ldy #>d1
jsr strout
lda $0297 ; 6551 status reg
ora #48
jsr chrout
lda #13
jsr chrout
lda #<d2
ldy #>d2
jsr strout
ldx $029c ; start rec buffer
lda #$00
jsr linprt
lda #13
jsr chrout
lda #<d3
ldy #>d3
jsr strout
ldx $029b ; end receive buffer
lda #$00
jsr linprt
lda #13
jsr chrout
lda #<d4
ldy #>d4
jsr strout
ldx $38 ; end of user ram
lda #$00
jsr linprt
lda #13
jsr chrout
jmp loop
d1 .text "6551 status register "
.null "at address $297: "
d2 .text "rs-232 start "
.null "receive buffer $29c: "
d3 .text "rs-232 end "
.null "receive buffer $29b: "
d4 .text "pointer to end of "
.null "basic ram: "
Abschließend die noch fehlenden Zeichenketten für die Bildschirmausgabe:
errormsg .null "error: buffer overrun!"
welcome .text "terminal ready"
.byte 13
.text "waiting for input from "
.null "serial line: "
.end
Startet man nun auf dem C64/C128 das Programm und hat am PC ein verbundenes Terminal-Programm am Laufen (ich verwende PuTTY), dann erscheinen die am PC eingetippten Zeichen

am Bildschirm des C64/C128. Allerdings wird erst die Willkommensbotschaft „terminal ready, waiting for input from serial line“ ausgegeben:

Nach jedem vom PC gesendeten „Return“ habe ich die „D“-Taste gedrückt und somit die Debug-Meldung erhalten. Nach der dritten Eingabe hatte ich das Programm mit „X“ beendet und damit wieder die Eingabeaufforderung „ready.“ von Basic bekommen.
Die Debug-Meldung ist nicht schön, sagt aber ein bisschen etwas aus über den Zustand unserer seriellen Verbindung zum PC. Die erste Zeile mit dem Wert „8“ ist der Wert der Adresse $297 (RSSTAT). Das gesetzte Bit 3 ergibt den Wert %1000 (8) und bedeutet „Receive Buffer Empty“. Klingt erst einmal komisch, macht aber Sinn, wenn man weiß, dass die gesendeten Zeichen (wie „hello from your pc“ bereits mittels GETIN-Befehl aus dem Puffer gelesen wurden. Dass der Pufferzeiger immer weiter verschoben wird, sieht man an den Adressen $29c und $29b. Nach dem „hello from your pc“ steht sowohl der Anfangs- als auch der Endzeiger auf dem Wert „190“. Nach dem Abholen des Zeichens „1“ (gefolgt von einer Null-Terminierung) werden beide Zeiger folglich um zwei Stellen weiter verschoben auf „192“ („1“ plus $00). Das gleiche passiert dann nach dem Abholen des Zeichens „2“. Jetzt stehen beide Zeiger auf dem Wert „194“.
Der Zeiger an Adresse $38 (MEMSIZ) auf das Ende des RAM-Bereichs steht auf 121. Damit ist die letzte verfügbare Adresse im RAM 121 * 256 -1 = 30975 ($78FF). Dazu muss man folgendes verstehen: Startet man einen C64 oder einen C128 im C64-Modus und erhält die Eingabeaufforderung von Basic, so steht die Adresse $38 noch auf dem Wert „160“: 160 * 256 -1 = 40959 ($9FFF). Lädt man nun den Assembler TMP v1.2 in den Hauptspeicher (RAM), dann findet sich in Adresse $38 der reduzierte Wert „121“: 121 * 256 – 1 = 30975 ($78FF), TMP hat also Speicherplatz im RAM ab Adresse $78FF reserviert, um den Assembler-Code des Anwenders unterzubringen. An Adresse $8000 befindet sich dann die Einsprungadresse für TMP selbst.
Es wäre jetzt relativ leicht den Code weiterzuentwickeln und eine echte Client/Server-Applikation zu bauen: der PC sendet ein Kommando wie „clear“ per Terminalprogramm und serieller Leitung an den C64/C128. Dieser liest „clear“ aus dem Empfangs-Puffer aus, interpretiert dies als einen Befehl an sich als Server und führt den folgenden, einfachen Code zum Löschen des Bildschirms aus:
lda #147 ; clear screen
jsr chrout
Hat man diesen Schritt gemeistert, ist es nicht mehr weit zum eigenen Bulletin Board-System 😉