Nikolaus Heusler Archiv

Erschienen in 64'er Magazin, Ausgabe 11/1993 · Originaldatei: RS232.TXT

Hinweis: Dies ist das an die Redaktion eingereichte Manuskript, nicht der gedruckte Endtext. Layout, Bildunterschriften, Korrekturen und Kürzungen der Redaktion können in der veröffentlichten Fassung abweichen.

Der singende Draht

Im Handbuch des C 64 steht gar nichts, im Programmierhandbuch nichts Genaues. Wie nutzt man die eingebaute RS 232-Schnittstelle? Wir stellen komplette Lösungen in Maschinensprache vor. Ein kurzes Zusatzprogramm erlaubt die Übertragung bis zu 4800 Bit/s.

Nikolaus M. Heusler

Die genormte RS 232-Schnittstelle arbeitet mit einer bitseriellen asynchronen Übertragung. Das bedeutet, ein Byte wird in Bits zerlegt, die nacheinander über die Leitung geschickt werden. Die asynchrone Übertragung verwendet Kennzeichen für Byteanfang und -ende. Bei manchen Computern ist auch eine synchrone Übertragung möglich, über eine eigene Leitung wird dann der Bittakt vorgegeben.

An dieser Stelle wollen wir einen kurzen Abriß über die Anwendung der RS 232-Schnittstelle geben. RS 232 eignet sich beispielsweise zur Übertragung von Daten zum PC. Dieser wird dazu an den Userport angeschlossesn. Problem: PCs erwartet die Daten mit einem anderen Spannungspegel als ihn der C 64 liefert. Adapter wie beispielsweise in 64'er 9/93 Seite 78 abgebildet setzen die physikalischen Pegel in die geforderten Werte um.

Die Schnittstelle kann die eine normale Daten mit OPEN geöffnet werden. Die Geräteadresse lautet 2, die Sekundäradresse ist ohne Relevanz. Wichtig ist der Dateiname. Er besteht aus zwei, in besonderen Fällen aus vier Zeichen. Das erste Byte des Dateinamens wird »Steuerregister« genannt. Seine acht Bit bestimmen das Format der Datenübertragung:

Bit 7: 0: ein Stopbit, 1: zwei Stopbits

Bit 5,6:	00: acht Datenbits
	01: sieben Datenbits
	10: sechs Datenbits
	11: fünf Datenbits

Bit 4: ohne Bedeutung

Bits 0 bis 3: Bitrate (Baud = Bit pro Sekunde)

	0000: selbstdefiniert
	0001: 50 Baud
	0010: 75 Baud
	0011: 110 Baud
	0100: 134,5 Baud
	0101: 150 Baud
	0110: 300 Baud
	0111: 600 Baud
	1000: 1200 Baud
	1001: 1800 Baud
	1010: 2400 Baud

Da das Steuerregister als Zeichen des Dateinamens verstanden wird, erfolgt die Angabe üblicherweise in Basic mit CHR$. Der Wert 138 beispielsweise lautet in binärer Darstellung 10001010 und bewirkt laut obiger Tabelle zwei Stopbits, acht Datenbits, eine Rate (Übertragungsgeschwindigkeit) von 2400 Bit pro Sekunde. Wird für die Rate der Wert 0000 gesetzt, hat der Dateiname vier Zeichen, wobei das dritte und vierte Zeichen die Baudrate festlegen. Wie das genau funktioniert, werden wir noch lernen.

Das zweite Zeichen des Filenamens enthält das sogenannte Befehlsregister« mit folgender Bitbelegung:

Bit 7,6,5: Parität

	000: keine Parität
	001: gerade Parität
	011: ungerade Parität
	101: achtes Datenbit 1
	111: achtes Datenbit 0

Bit 4: 0 = Vollduplex, 1 = Halbduplex

Bit 1,2,3: nicht benutzt

Bit 0: 0 = 3-Line Software-Handshake, 1 = X-Line Hardware-Handshake

Auch hier ein Beispiel: Der Wert 97 (binär 01100001) schaltet bei ungerader Parität und Vollduplex auf X-Line Hardware-Handshake.

Eine Datei öffnet in Basic beispielsweise folgender Befehl:

OPEN 2,2,2,CHR$(138)+CHR$(97)

Das Steuerregister hat den Wert 138, das Befehlsregister lautet 97.

Die Daten können wie bei jeder anderen Datei mit den gewohnten Befehlen gesendet und empfangen werden.

Schauen wir uns ein Beispiel an, bei dem in Maschinensprache eine solche Schnittstelle geöffnet wird:

LDA #2	; logische Filenummer
LDX #2	; RS 232 Gerätenummer
LDY #3	; irgendeine Sekundäradresse
JSR $FFBA	; SETPAR
LDA #2	; Länge des Dateinamens
LDX #<FADR	; Lowbyte der Adresse des Filenamens

LDY #>FADR

JSR $FFBD	; SETNAM
LDX #<EBUF	; Lowbyte Empfangspuffer
LDY #>EBUF	; Highbyte
STX $f7	; setzen

STY $f8

LDX #<ABUF	; Lowbyte Ausgabepuffer
LDY #>ABUF	; Highbyte
STX $f9	; setzen

STY $fa

JSR $FFC0	; OPEN
LDX #2	; Ausgabekanal öffnen
JSR $FFC9	; CHKOUT
...	; Datenausgabe mit JSR $FFD2
JSR $FFCC	; CLRCHN

LDA #2

JSR $FFC3	; CLOSE

RTS

; Dateiname: zwei Bytes

FADR .BYTE %00000110	; Wert für Steuerregister
     .BYTE %00000000	; Wert für Befehlsregister

Im C 64 erfolgt rein softwaremäßig die Steuerung über die CIA 2. Dieser Baustein besitzt zwei 16 Bit-Intervalltimer, die von einem bestimmten Wert auf Null abwärts zählen und dann ein Signal (NMI) auslösen. Die im C 64 bereits eingebaute Software nutzt dies aus. Sie holt den Wert für den Timer B, der als Interruptquelle dient, aus einer im ROM gespeicherten Tabelle. Danach startet der Timer.

Da beim C 64 das Senden und Empfangen via RS 232 interruptsgesteuert abläuft, werden zwei Puffer benötigt. Einer der beiden Zwischenspeicher steht für die zu sendenden Daten bereit. Die Daten werden nur in den Puffer geschrieben. Das Senden erfolgt mit Hilfe des NMI-Interrupts, weil man den (im Gegensatz zum IRQ) nicht sperren kann. Dadurch ist gewährleistet, daß die Daten in jedem Fall gesendet werden. Da das Beschreiben des Pufferns normalerweise schneller geht als das Senden der Zeichen, sollte man nachdem ein Zeichen abgeschickt wurde das nächste Zeichen vorbereiten und dann warten, bis die Sendung erfolgt ist. Beachtet man das nicht, könnte versehentlich ein noch nicht gesendetes Zeichen überschrieben werden, die Folge wären Übermittlingsfehler.

Das Empfangen geschieht auf ähnliche Weise, denn die Leitung RXD, über die Daten ankommen, ist nicht nur mit dem Port der CIA 2 verbunden, sondern auch mit der Leitung FLAG2, wodurch bei jedem eingehenden Datum ein NMI ausgelöst wird. Das Empfangen der Daten erfolgt programmunabhängig, die Daten werden in den Empfangspuffer geschrieben, der genauso groß ist wie der Sendepuffer: 256 Byte. Man sollte sich also rechtzeitig darum kümmern, daß die Daten aus diesem Zwischenspeicher abgeholt werden. Im obigen Beispiel haben wir gesehen, daß man dem C 64 in den Speicherzellen $f7 bis $fa vor der Verwendung der RS 232 die Adressen der beiden Puffer mitteilen muß. Übrigens werden die Pufferzeiger auch vom Betriebssystem gesetzt. Dieses legt die Puffer jedoch an das obere Ende des Basicspeichers. Aus diesem Grund wird beim Öffnen der Schnittstelle ein CLR-Befehl ausgeführt, denn im gleichen Speicherbereich liegen ja auch die String-Variablen. Um das zu vermeiden, legen wir die Puffer in den sonst unbenutzten Speicherbereich von $CE00 bis $CFFF. Das sollte normalerweise vor dem OPEN-Befehl geschehen, weil das Betriebssystem die Pufferadressen erst festlegt, nachdem es in den Speicherzellen $F8 und $FA nachgeschaut hat, ob das vom Programm schon erledigt wurde. Ist das Highbyte der Pufferzeiger ungleich Null, geht das System davon aus, daß die Zeiger bereits auf die Puffer deuten. Dann wird auch nichts verändert.

Durch die Kernalroutine READST ($FFB7) oder beim C 64 durch Auslesen der Speicherzelle 144 ($90) läßt sich der Status abfragen. Die einzelnen Bits haben folgende Bedeutung:

Bit 0: Paritätsfehler (Prüfsumme falsch)

Bit 1: Rahmenfehler

Bit 2: Empfangspuffer voll

Bit 3: Empfangspuffer leer (testen nach GET#)

Bit 4: CTS-Signal fehlt

Bit 5: nicht benutzt

Bit 6: DSR-Signal fehlt

Bit 7: Abbruch

Ein gesetztes Bit signalisiert, daß die jeweilige Störung aufgetreten ist. Bei gesetztem Bit 2 zum Beispiel lief der Empfangspuffer über.

Um nun andere als die vorgesehenen Übertragungsraten zu benutzen, könnten natürlich die Werte in den Timer und die entsprechenden Werte in die beiden Speicherzellen geschrieben werden. Nachteil: Dieses Programm würde nur auf dem C 64 arbeiten, auf keinem anderem Commodore-Computer. Aber es geht auch eleganter: Über die selbst definierte Bitrate!

Dazu müssen die unteren vier Bits des ersten Bytes im Filenamen (das Steuerregister) den Wert Null haben, die Bitrate steht dann im dritten und vierten Byte des Dateinamens. Die Werte berechnet man nach folgender Formel:

RATE = TAKT/WERT/2-100

RATE_HIGH = INT (RATE / 256)

RATE_LOW = RATE - RATE_HIGH * 256

Mit WERT ist die gewünschte Übertragungsrate in Bit/s (zum Beispiel 1200) gemeint, TAKT gibt den Prozessortakt in Hz an. Ein deutscher C 64 hat TAKT = 985250, in Amerika gilt TAKT = 1022730. Für 1200 Baud würde das Zwischenergebnis RATE = 311 (gerundet) betragen. Diese Zahl wird nach den bekannten Formeln in High- und Lowbyte umgerechnet, das Ergebnis ist: Highbyte = 1, Lowbyte = 55. Als drittes und viertes Byte im Filenamen würden Sie also CHR$(55)+CHR$(1) schreiben.

Beachten Sie zweierlei: Erstens wird der Datei-»Name« hier nicht wie beispielsweise bei der Floppy zum Peripheriegerät geschickt, sondern in einzelne Zeichen zerlegt, die den weiteren Ablauf steuern. Zweitens gelten wie gesehen in USA und Europa (bedingt durch unterschiedliche Normen in der Video-Bilderzeugung) unterschiedliche Taktfrequenzen. Wenn Sie eine der vorgegebenen Baudraten auswählen möchten, brauchen Sie die Berechnung mit der RATE nach obiger Formel ja nicht selbst auszuführen. Im ROM befinden sich zwei Tabellen, in der die Werte für High- und Lowbyte für alle voreingestellten Übertragungsraten stehen. Welche Tabelle der C 64 benutzt, hängt von der benutzten Version ab. Auch Sie als Anwender können ganz einfach durch Auslesen der Speicherzelle $2A6 (678) feststellen, welche Version Sie vor sich haben: Bei einem amerikanischen C 64 (Video-Norm NTSC) steht hier eine Null, in Europa (PAL) eine Eins.

Als Übung wollen wir ein File mit 4800 Bit/s (also mit selbst definierter Baudrate) übertragen (7 Datenbits, 1 Stopbit, Vollduplex, 3-Line-Handshake, keine Parität). Nach obiger Tabelle ergeben sich für das Steuerregister der Wert %00100000 = 32 = $20, das Befehlsregister lautet %00000000 = $00. Nach obiger Formel gilt RATE_HIGH = 0, RATE_LOW = 2. Auf die Unterscheidung zwischen PAL und NTSC verzichten wir. Das Programm unterscheidet sich kaum von dem altbekannten:

LDA #2	; logische Filenummer
LDX #2	; RS 232 Gerätenummer
LDY #3	; irgendeine Sekundäradresse
JSR $FFBA	; SETPAR
LDA #4	; Länge des Dateinamens
LDX #<FADR	; Lowbyte der Adresse des Filenamens

LDY #>FADR

JSR $FFBD	; SETNAM
LDX #<EBUF	; Lowbyte Empfangspuffer
LDY #>EBUF	; Highbyte
STX $f7	; setzen

STY $f8

LDX #<ABUF	; Lowbyte Ausgabepuffer
LDY #>ABUF	; Highbyte
STX $f9	; setzen

STY $fa

JSR $FFC0	; OPEN
LDX #2	; Ausgabekanal öffnen
JSR $FFC9	; CHKOUT
...	; Adresse »PT« auf Dateianfang setzen

SENDE LDY #0

LDA (PT),Y	; ein Zeichen aus Datei lesen
...	; Zeichen bearbeiten (z.B. Codewandlung)
TAX	; Zeichen merken

WART LDA $2A1 ; Status lesen

AND #1	; Bit 1 testen
BNE WART	; nicht bereit, dann warten

TXA

JSR $FFD2	; CHROUT Zeichen senden

weiter bei (SENDE), bis Dateiende erreicht

JSR $FFCC	; CLRCHN

LDA #2

JSR $FFC3	; CLOSE

RTS

; Dateiname: vier Bytes

FADR .BYTE $20 ; Wert für Steuerregister

.BYTE $00 ; Wert für Befehlsregister

.BYTE $02 ; RATE_LOW

.BYTE $00 ; RATE_HIGH

Als kleines Demoprogramm geben Sie Listing 1 bitte mit dem MSE ein. Das Programm sendet sequentielle Dateien mit 4800 Bit/s, 8 Datenbits, Vollduplex, keine Parität, 1 Stoppbit, 3-Line Handshake. Die Syntax lautet:

SYS 52480, KONV_FLAG, "NAME"

Wenn KONV_FLAG = 0, werden die Zeichen der Datei erst noch in den ASCII-Code gewandelt, bei KONV_FLAG = 128 erfolgt die Übertragung unverändert.

Mit "NAME" ist der Titel der sequentiellen Datei gemeint. Beispiel:

SYS 52480, 128, "TEST,S,R"

"TEST" wird unkonvertiert gesendet.

(pk)

; dokumentierter Quelltext zu SEQ-TRANS.OBJ

; Kommentar von Nikolaus Heusler

; alle Zahlenwerte hexadezimal

; Konv-Flag lesen und speichern

cd00 jsr aefd	; CHKCOM Komma holen
cd03 jsr b79e	; GETBYT Konv-Flag holen nach X
cd06 stx   02	; und zwischenspeichern

; Dateinamen festsetzen

cd08 jsr aefd	; CHKCOM Komma holen
cd0b jsr ad9e	; FRMEVL Ausdruck auswerten
cd0e jsr b6a6	; FRESTR String (Dateinamen) lesen
cd11 cmp  #05	; Länge ≥ 5 ?
cd13 bcs cd18	; ja, dann OK
cd15 jmp af08	; sonst ?SYNTAX ERROR
cd18 ldx  $22	; Adresse des Dateinamens low
cd1a ldy  $23	; und high
cd1c jsr ffbd	; SETNAM Namen festlegen

; SEQ-Datei von Diskette lesen

cd1f lda  #01	; Filenummer 1
cd21 ldx  #08	; Geräteadresse 8 = Floppy
cd23 ldy  #02	; Sekundäradresse 2
cd25 jsr ffba	; SETPAR
cd28 jsr ffc0	; OPEN Datei zum Lesen öffnen
cd2b jsr ffb7	; Status holen
cd2e beq cd38	; Fehler? Nein, dann OK

; Fehlerbehandlung

cd30 jsr ffcc	; CLRCHN Kanal schließen

cd33 lda #01

cd35 jmp ffc3	; CLOSE 1, Abbruch
cd38 ldx  #01	; Filenummer
cd3a jsr ffc6	; Eingabekanal schalten
cd3d jsr ffb7	; Status holen
cd40 bne cd30	; Fehler, dann Abbruch
cd42 ldx  #01	; Datei nach $801 laden

cd44 ldy #08

cd46 stx  $22	; Pointer in Speicher

cd48 sty $23

cd4a jsr ffcf	; BASIN ein Zeichen aus Datei lesen
cd4d ldy  #00	; und speichern

cd4f sta (22),y

cd51 inc  $22	; Zeiger auf nächste
cd53 bne cd57	; Speicherzelle

cd55 inc $23

cd57 jsr ffb7	; Status holen
cd5a beq cd4a	; OK, dann weiter
cd5c and  #40	; Bit 6 testen: End of File?
cd5e beq cd30	; nein, dann Fehler

; Datei komplett gelesen

cd60 jsr cd30	; Datei schließen
cd63 lda  #01	; Dateinummer
cd65 ldx  #02	; Geräteadresse 2 = RS 232
cd67 ldy  #03	; Sekundäradresse 3 (keine Bedeutung)
cd69 jsr ffba	; SETPAR
cd6c lda  #04	; Dateiname: 4 Zeichen
cd6e ldx  #e5	; Zeiger auf Adresse $CDE5
cd70 ldy  #cd	; (dort steht Dateiname)
cd72 jsr ffbd	; SETNAM
cd75 jsr ffc0	; OPEN
cd78 ldx  #03	; vier Bytes ab $cde9 kopieren

cd7a lda cde9,x

cd7d sta   f7,x	; Zeiger auf Puffer

cd7f dex

cd80 bpl cd7a	; alle vier Bytes kopieren
cd82 ldx  #01	; Datei 1 als Ausgabekanal schalten
cd84 jsr ffc9	; CHKOUT entspricht CMD 1
cd87 ldx  #01	; Zeiger auf Dateispeicher $801
cd89 ldy  #08	; Highbyte
cd8b stx  $14	; nach $14, $15 schreiben

cd8d sty $15

cd8f lda dc0e	; CIA Nr. 1 Kontrollregister A
cd92 and #$fe	; Bit 0 löschen
cd94 sta dc0e	; Timer A abschalten

; Datei übertragen

cd97 ldy #00

cd99 lda  (14),y	; ein Byte aus Speicher lesen
cd9b bit   02	; konvertieren?
cd9d bmi cdb4	; nein, dann weiter

; Konverter CBM ASCII PC-Ascii

cd9f cmp #$41	; dez. 65 (kleines a)
cda1 bcc cdb2	; kleiner, dann übernehmen
cda3 cmp  #80	; dez. 128
cda5 bcs cdab	; größer, dann kein Kleinbuchstabe
cda7 ora  #20	; sonst Bit 5 setzen
cda9 bne cdb2	; unbedingter Sprung
cdab cmp  #c0	; dez. 192
cdad bcc cdb2	; kleiner, dann kein Großbuchstabe
cdaf sec		; 128 abziehen

cdb0 sbc #80

cdb2 and  #7f	; Bit 7 löschen

; Byte senden

cdb4 tax		; in X zwischenspeichern
cdb5 lda  2a1	; Sendestatus
cdb8 and  #01	; schon bereit?
cdba bne cdb5	; nein, dann warten
cdbc txa		; Byte zurückholen
cdbd jsr ffd2	; und senden
cdc0 inc   14	; nächste Speicherzelle
cdc2 bne cdc6	; adressieren

cdc4 inc 15

cdc6 lda   22	; letzte Speicherzelle
cdc8 cmp   14	; schon erreicht?
cdca bne cd97	; nein, dann weitersenden

cdcc lda 23

cdce cmp 15

cdd0 bne cd97	; sonst beenden

; Ende der Übertragung

cdd2 lda  2a1	; Sendestatus
cdd5 and  #01	; fertig?
cdd7 bne cdd2	; nein, dann warten
cdd9 jsr cd30	; sonst Datei schließen
cddc lda dc0e	; und Timer wieder einschalten

cddf ora #01

cde1 sta dc0e

cde4 rts		; fertig

; ab hier folgen vier Bytes des Dateinamens für die RS 232

cde5 .byte $00	; Steuerregister (Wert: Null)
		; hier:	ein Stopbit
			acht Datenbits
			freie Übertragungsrate
cde6 .byte $00	; Befehlsregister (Wert: Null)
		; hier:	keine Pariät
			Vollduplex
			3-Line Handshake
cde7 .byte $02	; Lowbyte Übertragungsrate
cde8 .byte $00	; Highbyte Übertragungsrate
cde9 .word $ce00	; Zeiger auf Empfangspuffer
cdea .word $cf00	; Zeiger auf Ausgabepuffer