C64-Netzwerk mit dem seriellen Bus

Ziel dieses kleinen Projektes ist die Vernetzung mehrerer C64 - theoretisch bis zu sieben - unter Verwendung des seriellen Ein-/Ausgangs.

 

Software

Download: serial.zip

Auf dem Disk-Image sind Programmfiles, die zu verschiedenen Programmgruppen gehören:

 

Hardware

Die Vernetzung über den seriellen Bus hat Vor- und Nachteile. Die Ausgänge des Inverters/Treibers 7406 sind mit bis zu 40mA belastbar, sie sind also sehr viel robuster als die Ausgänge des Userports und erlauben auch durchaus längere Leitungswege ohne zusätzliche Treiberbausteine. Nachteile sind die Langsamkeit der Übertragung und eine ziemliche Umständlichkeit bei der Handhabung, da der C64 von Haus aus dafür konzipiert ist, das ausschließliche Kommando über den Bus zu führen. Das Konzept der Gerätenummer ist auf ihn nicht anwendbar, die Kernal-Bus-Routinen können nicht verwendet werden, und für die Datenübertragung stehen nur zwei Leitungen zur Verfügung. Zwar hat der serielle Bus neben GND und RESET eigentlich vier Leitungen, jedoch ist ATN nur als Ausgang und SRQIN nur als (vom System ungenutzter) Eingang verfügbar, sodaß bloß die zwei Leitungen CLOCK und DATA überbleiben, über die gesendet und empfangen werden kann.

Jedes Gerät am Bus, egal ob C64 oder Floppy, belastet den Ausgang mit 5mA, daraus resultiert eine theoretische Obergrenze von 8 Geräten. Da ich das Aus- und Einlöten von ICs hasse, habe ich mich nie an das praktische Ausloten dieser Grenze gewagt. Zusätzlich belasten Kabelkapazitäten die Übertragung. Die bisherigen Tests haben ergeben, daß eine Gesamtlänge der seriellen Kabel von 25 Meter bei 5 Geräten am Bus problemlos funktioniert. Dabei habe ich eine abgeschirmte Steuerleitung mit 7 Adern verwendet, und darauf geachtet, daß im Kabel die signalführenden Leitungen Clock, Data und ATN nicht unmittelbar nebeneinander liegen, sondern "neutrale" Leitungen wie GND, SRQIN und Reset dazwischenliegen. Die Reset-Leitung habe ich gar nicht erst verbunden, da es keine Notwendigkeit für einen Hardware-Reset gibt und damit Probleme mit älteren C64 wegfallen. Über 25 Meter bin ich bei den Tests nicht gegangen. Eventuell ließen sich auch weit größere Entfernungen überbrücken, wenn für Clock, Data und ATN einzeln abgeschirmte Kabel mit geringen Kabelkapazitäten (Koaxkabel) verwendet würden.

Da der C64 keine zweite serielle Buchse zum Weiterverbinden hat, läßt sich mit Standard-Kabeln nur ein Netzwerk aus zwei C64 verwirklichen. Für eine größere Anzahl von Rechnern sind Weichen erforderlich. Die Lösung, bei der man am wenigsten verkehrt machen kann, sieht so aus, daß man mehrere 6-polige DIN-Buchsen in ein Gehäuse einbaut und die Buchsen parallel miteinander verschaltet. An diese Steckverteiler kann man nun Standardkabel für den seriellen Bus anschließen bzw. Kabel mit Sonderlängen selber herstellen. Die Pin-Belegung für den seriellen Anschluß ist im C64-Bedienungshandbuch auf Seite 142 oder hier zu finden.

Logik:

Die Leitungen am Bus werden im "Ruhezustand" durch einem Widerstand (1k) passiv auf 5V gehalten. Nach Jim Butterfields Artikel HOW THE VIC/&$ SERIAL BUS WORKS wird dieser Zustand als "false" bezeichnet. Wird der Ausgang aktiviert, zieht der Treiber die Leitung auf Masse, also auf annähernd 0V. Dieser Zustand nennt sich "true". Jedes der Geräte am Bus kann eine Leitung auf "true" legen, und dieser Zustand kann von allen registriert werden, da der Pegel der Leitungen parallel als Eingang abgefragt wird. Bei verschiedenen Outputs gilt immer die Regel: "true" schlägt "false". Hält ein Gerät die Leitung auf "true", ist es völlig egal was die anderen machen. Mehr als "true" kann sie nicht werden. Über den Output der anderen Geräte läßt sich in dem Fall also keine Feststellung treffen. Der Umstand, daß der Ausgang durch den Treiber invertiert wird, macht die Sache noch ein Stück verwirrender. Die Ein-/Ausgabe findet in einem einzigen Register statt (das nebenbei auch die Speicherbank für den VIC verwaltet):

CIA2 Port A Datenregister 56576 ($DD00):

Bit 7 6 5 4 3 2 1 0
Wert 128 ($80) 64 ($40) 32 ($20) 16 ($10) 8 ($08) 4 ($04) 2 ($02) 1 ($01)
Inhalt Data
Input
Clock
Input
Data
Output
Clock
Output
ATN
Output
RS232 VIV
Mem
VIC
Mem
Zustand 0=true
1=false
0=true
1=false
0=false
1=true
0=false
1=true
       


Netzwerk einrichten:

Zuerst die C64 und eine Floppy mit seriellen Kabeln verbinden, dann die Geräte einschalten. Bei C64 älteren Baujahrs bewirkt die Reset-Leitung, daß der erste Rechner nicht "hochkommt" da er einen permanenten Reset empfängt, solange ein zweiter, ausgeschalteter C64 am Bus hängt. Bei neueren C64 (ab ca. 1985) fällt dieser Umstand weg, da der Reset vom seriellen Bus nicht durchgeschaltet wird.

Im Einschaltzustand hält der C64 die Clock-Line true. Jeder Rechner blockiert so für den anderen den Floppy-Zugriff. Man muß nun an einem der beiden Rechner eintippen

SYS 61061 (das ist leichter zu merken als POKE 56576,199 , was das gleiche bewirkt)

damit man mit dem anderen das Programm laden kann. Ist es fertig geladen, kann ohne weitere Umstände das Programm auch in den zweiten C64 geladen werden. Haben beide Rechner geladen, kann die Floppy ruhig weiter am Bus bleiben, den Datenaustausch zwischen den beiden C64 berührt das nicht im geringsten.

 

Wechselseitiger Datenaustausch zwischen 2 C64

Diese kleinen Programme sind eine Art Zwischenergebnis. Um erste Erfahrungen mit dem Problem zu sammeln, begann ich mit einer seriellen Verbindung zwischen zwei C64. Etwas derartiges gibt es übrigens schon lange: C64 SERIAL TALK auf der -=> The Singular Crew's homepage! <=- . Neu ist vielleicht nur, daß die Routinen hier universell für verschiedene Anwendungen einsetzbar sind.

Für ein Netzwerk mit mehr als 2 Teilnehmern müßte ein Identifikationssystem eingerichtet werden. Das habe ich im Programm "sermes" auch gemacht. Den Ansatz universell einsetzbarer Routinen habe ich dann aber nicht weiterverfolgt, der ist also bei der Übertragung zwischen 2 C64 steckengeblieben.

Zu dieser Programmgruppe gehören folgende Files:

Das Maschinenprogramm "serial2" ist 416 Bytes lang und läuft in jedem Speicherbereich, ohne daß eine Anpassung nötig wäre. Es gibt 4 einzeln aufrufbare Subroutinen. Die Einsprungadresse bezieht sich auf die Basis- bzw. Ladeadresse des Programms. Die Originaladresse ist 49152 ($C000)

  • BA + 0
Connect Macht den Rechner zum Teilnehmer am Bus
  • BA + 3
Get Message Empfängt eine Nachricht beliebiger Länge vom anderen Rechner
  • BA + 6
Send Message Sendet eine Nachricht beliebiger Länge an den anderen Rechner
  • BA + 9
Disconnect Gibt den Bus frei (z.B. für Diskzugriff)


Vorbereitungen:

Das Programm benutzt die Zero-Page Adressen 163-176 ($A3-B0), die vom Betriebssystem sonst für Input/Output benutzt werden.

Dez. Hex. Inhalt
163 $A3 Hilfszähler
164 $A4 Hilfsspeicher
165 $A5 ID-Byte (verhindert paralleles Senden)
166,167 $A6,$A7 Zeiger auf Anfang ausgehende Nachricht (Low-Byte, High-Byte) *
168.169 $A8,$A9 Zeiger auf Ende (exklusive) ausgehende Nachricht (Low-Byte, High-Byte ) *
170,171 $AA,$AB Zeiger auf Anfang eingehende Nachricht (Low-Byte, High-Byte) *
172,173 $AC,$AD Zeiger auf Ende (exklusive) eingehende Nachricht (Low-Byte, High-Byte)
174,175 $AE,$AF Hilfszeiger beim Senden
176 $B0 Zeitlimit *

* Die mit einem Stern gekennzeichneten Register müssen vor Aufruf der Routinen gesetzt werden.

Die Zeiger für Anfang und Ende der ausgehenden Nachricht bestimmen die Länge der Nachricht - es kann ein einziges Byte gesendet werden oder praktisch der gesamte RAM-Inhalt.

Der Zeiger auf den Anfang der eingehenden Nachricht bestimmt, wo sie gespeichert werden soll. Der Zeiger auf das Ende wird vom Programm gesetzt und teilt die Länge der empfangenen Nachricht mit.

Das Register Zeitlimit legt fest wie lange gewartet werden soll, wenn der andere Rechner nicht empfangsbereit ist. Ein Wert von Null bedeutet, daß ganz ohne Limit auf die Empfangsbereitschaft gewartet wird. Ein Wert zwischen 1 und 255 legt eine Zeit fest, nach deren Ablauf ein Sendeversuch abgebrochen wird. Die Zeit in Sekunden beträgt etwa X*X/330 und kann folglich zwischen 3 Millisekunden und 3 Minuten liegen.

Statusflags:

Die Routinen verwenden das I/O Statusregister 144 ($90), Variable ST, jedoch mit anderer Bedeutung der gesetzten Bits. Ein gesetztes Bit 7 bedeutet einen Error, ansonsten handelt es sich um informative Rückmeldungen.

Bit Wert dez. Wert hex. Titel Bedeutung
7 128 $80 Error Die genauere Fehlerursache ist den Bits 0 und 1 zu entnehmen
6 64 $40 Message received Eine Nachricht wurde empfangen und gespeichert
5 32 $20 Message not sent Die ausgehende Nachricht konnte nicht abgeschickt werden
4 16 $10 Online Der Rechner ist aktiv am Bus
3 8 $08 -  
2 4 $04 -  
1 2 $02 Timeout Bei Connect oder Send Message wurde das Zeitlimit überschritten
0 1 $01 Bus disordered Die Vorgänge am Bus entsprechen nicht dem Protokoll


Kommunikation:

Ehe irgendwelche Nachrichten verschickt werden können, müssen sich beide Rechner mit "Connect" als Teilnehmer am Bus anmelden. Damit signalisieren sie: ich bin online, aber derzeit nicht empfangsbereit. Von nun an ist übrigens für keinen der beiden Rechner ein Disk-Zugriff möglich!

Die Subroutine "Get Message" wird aufgerufen um zu überprüfen, ob eine Nachricht wartet. Wenn ja, wird sie an die angegebene Adresse verliefert und Bit 6 des Statusregisters gesetzt. Wenn nein, kehrt die Routine umgehend mit gelöschtem Bit 6 zurück.

Die Subroutine "Send Message" sendet die durch die Zeiger spezifizierte Nachricht. Genauer gesagt: sie versucht es. Sie muß nämlich warten bis der andere Rechner Empfangsbereitschaft signalisiert (und das tut dieser nur dann, wenn er "Get Message" absolviert). Zeigt der andere Rechner keine Empfangsbereitschaft, setzt die Routine nach Ablauf des Zeitlimits die Flags "Error", "Message not sent" und "Timeout" im Statusregister und kehrt unverrichteter Dinge zurück. Ein anderer Fall liegt vor, wenn der andere Rechner bereits zuvor das Signal gesetzt hat, daß er eine Nachricht senden möchte. In dem Fall tritt der Späterkommende zurück und empfängt die wartende Nachricht, anstatt seine eigene abzusetzen. Die Routine setzt die Flags "Message not sent" und "Message received", jedoch nicht das Error-Flag, da es sich um keinen eigentlichen Fehler sondern um ein lösbares Koordinationsproblem handelt. Wie auch immer, bei Benutzung der Routine "Send Message" ist danach das Statusflag zu prüfen und beim Flag "Message received" so zu verfahren wie bei der Routine "Get Message", nämlich die eingegangene Nachricht irgendwie zu verwerten. Die eigene, noch ungesendete Nachricht muß dann eben später abgesetzt werden.

Die Routine "Disconnect" gibt den Bus frei, um beispielsweise wieder auf die Floppy zugreifen zu können. Dazu muß "Disconnect" auf beiden Rechnern ausgeführt werden.

Je nachdem für welche Aufgaben die Routinen eingesetzt werden, können sich verschiedene Koordinationsprobleme ergeben. Wenn unklar ist, ob und wann Nachrichten vom anderen Rechner zu erwarten sind, muß permanent mit "Get Message" der Bus auf wartende Nachrichten überprüft werden, da der andere Rechner ansonsten mit dem Warten auf Empfangsbereitschaft blockiert ist. Die Routinen regeln den Datenaustausch auf sehr einfache Weise. Für eine feinere Koordination kann je nach Problemstellung ein Protokoll auf der nächsten Ebene entwickelt werden, indem Steuerbytes versandt werden, die bestimmte Anweisungen für den anderen Rechner enthalten.

Die Geschwindigkeit der Datenübermittlung beträgt etwa 700 Bytes/Sekunde und liegt damit etwa in der Größenordnung, die man von der Floppy gewohnt ist. Das größte Problem für das Tempo ist der VIC, der den Prozessor ausbremst und zu Verzögerungen führt, die einkalkuliert werden müssen. Das Timing wurde so eingestellt, daß aktive Sprites, die auch etwas Rechenzeit kosten, während des Zugriffs zulässig sind.


Einbindung in eigene Programme:

Das Programm enthält keinen internen absoluten Adressen, ist also in jedem beliebigen Speicherbereich lauffähig, es benutzt außer den genannten Zero-Page-Registern keine Ressourcen und verwendet keine Routinen des Betriebssystems.

Für die Einbindung in Assembler: Die Routinen schalten während der Ausführung den IRQ ab, bei der Rückkehr stellen sie diesbezüglich den anfänglichen Prozessor-Status wieder her. Ansonsten enthalten die Routinen nach Rückkehr das Statusbyte im Akku und haben Mi-Flag (Error) und V-Flag (Message received) gesetzt. Man kann die Routine "Get Message" in den IRQ einbinden, muß aber dann natürlich auch dafür Sorge tragen, daß eingegangene Nachrichten auch gleich richtig verliefert werden.


"basictest":

Dieses kleine Programm habe ich nur für Testzwecke geschrieben. Wenn beide C64 geladen haben, kann man es mit RUN starten. Nun empfängt man am Bildschirm jeden Tastendruck beider Rechner, dargestellt in verschiedenen Farben.

 

Einseitige Übermittlung von Joysticksignalen

Zu dieser Programmgruppe gehören folgende Files:

Sinn der Sache ist es, einen C64 zur Abfrage zusätzlicher Joystickeingänge einzusetzen (Joystickport 1 und 2 sowie Userport für den 4-Spieler-Adapter). Eine praktische Verwendung findet das im Spiel Protovision - Wor Wizards Tournament von BIG USER alias Stefan Gutsch.

Der C64 zum Abfragen der zusätzlichen Joystickports tut sonst nichts als die Eingänge zu prüfen und über den seriellen Bus zu senden. Sein Bildschirm ist aus Gründen des Timings abgeschaltet. Man kann es sich eigentlich ersparen, ihn an einen Monitor zu hängen, wenn man den Befehl zum Laden und Starten des Programms blind eintippt.

Das Programm zum Senden wartet auf eine Aufforderung durch das Empfangsprogramm, ehe es zu senden beginnt. Ansonsten verhält es sich still, sodaß für den empfangenden C64 ein Diskzugriff außerhalb der Empfangsroutine problemlos möglich ist. (Die spezielle Sendeaufforderung kommt im normalen Protokoll der Floppy-Kommunikation nicht vor - ob das auch für alle Fastloader zutrifft ?) Nachdem die Empfängerseite die Kommunikation mit der Floppy beendet hat, müssen mindestens 3328 Takte (ca. 3.3 Millisekunden) vergehen ehe die Empfangsroutine für die Joysticksignale aufgerufen werden kann, da der Sender erst nach einer Ruhepause am Bus wieder auf Sendeaufforderungen reagiert.

Die Routine "getjoy ml" kann sehr leicht in eigene Programme eingebaut werden. Sie ist in jedem Speicherbereich lauffähig, verwendet keine Arbeitsregister und erfordert nicht, daß der IRQ abgeschaltet wird. Da es nur ein Testprogramm ist, werden die empfangenen 4 Bytes im Bildschirmspeicher abgelegt (ab 1024 bzw. $0400), diese Ablageadresse muß an den jeweiligen Bedarf angepaßt werden.

Das Testprogramm "getjoy" zeigt die empfangenen Bytes am Bildschirm und liest bei einem Tastendruck den Titel des Directorys von Disk ein, um die Möglichkeit des parallelen Floppyzugriffs zu testen.

Listing

Die Routine "sendjoy ml" wartet bei Label "freebus" auf das Signal zum Senden, das aus einer chrarakteristischen Abfolge von Buszuständen besteht:

1. Signal: Data=true, Clock=true
2. Signal: Data=false, Clock=true
3. Signal: Data=false, Clock=false
4. Signal: Data=true, Clock=false
5. Signal: Data=false, Clock=false

Bei einer Abweichung von dieser Sequenz wird bei Label "watchbus" gewartet, bis der Bus für 3.3 Millisekunden den Zustand Data=false, Clock=false aufweist, also wieder frei ist.

Paßt das Signal, werden in der Vorbereitung "prepare" die 4 zu sendenden Bytes auf Stack gelegt. "loopbyte" wird für die Anzahl zu sendender Bytes durchlaufen, am Schluß wird das gesendete Byte vom Stack geholt. In "loopbit" werden zwei Bits je Durchlauf gesendet. Gesendet wird nach Takt des Empfängers, der die Clock-Leitung abwechsend true und false legt, bei jedem Wechsel setzt der Sender die Data-Leitung entsprechend dem zu sendenden Bit. Das Bit wird invertiert, sodaß es nach der Invertierung durch die Hardware beim Empfänger gleich "richtig" ankommt.

sendjoy ml

  sei Start, IRQ aus
  lda #$0b  
  sta $d011 VIC abschalten
  lda #$80 Bit 7 =1
  sta $dd03 Userport als Ausgang setzen (4-Spieler-Adapter)
freebus lda #$07 Warteschleife, Bus freigeben
  sta $dd00 Data=false, Clock=false
wait1 lda $dd00 Warten auf Signal Senden 1
  cmp $dd00 entprellen
  bne wait1 warten
  cmp #$c7 Data=false, Clock=false?
  beq wait1 ja, warten
  and #$c0 Data und Clock isolieren
  bne watchbus nicht beide true, Bus beobachten
wait2 lda $dd00 Warten auf Signal Senden 2
  and #$c0 Data und Clock isolieren
  beq wait2 beide true, warten
  bpl watchbus Data true, Bus beobachten
wait3 bit $dd00 Warten auf Signal Senden 3
  bpl watchbus Data true, Bus beobachten
  bvc wait3 Clock=true, warten
wait4 bit $dd00 Warten auf Signal Senden 4
  bvc watchbus Clock=true, Bus beobachten
  bmi wait4 Data=false, warten
wait5 bit $dd00 Warten auf Signal Senden 5
  bvc watchbus Clock=true, Bus beobachten
  bpl wait5 Data=true, warten
  bit $dd00 Endprüfung
  bvs prepare Clock=false, Sendung vorbereiten
watchbus ldy #$00 Bus beobachten, Disk-Zugriff abwarten
wait6 lda $dd00 Loop Bus beobachten
  cmp #$c7 Data=false, Clock=false?
  bne watchbus nein, Beobachtung neu starten
  iny  
  bne wait6 zu Loop [13*256 Takte = 3.3 Millisekunden]
  dey Status mi
passfree bmi freebus zur Warteschleife (Weiterleitung Ende Loop Byte)
prepare lda $dc01 Sendung vorbereiten, Joystick Port 1
  and #$1f Richtung und Feuerknopf isolieren
  pha Sendbyte 1 auf Stack
  lda $dc00 Joystick Port 2
  ldx #$80 Bit 7=1
  stx $dd01 Userport Ausgang setzen
  and #$1f Richtung und Feuerknopf isolieren
  pha Sendbyte 2 auf Stack
  lda $dd01 Userport auslesen
  ldx #$00 Bit 7=0
  stx $dd01 Userport Ausgang setzen
  and #$1f Richtung und Feuerknopf isolieren
  pha Sendbyte 3 auf Stack
  lda $dd01 Userport auslesen
  asl a Bit 5 nach Bit 4 verschieben...
  asl a  
  asl a  
  php  
  asl a  
  plp  
  ror a  
  lsr a  
  lsr a  
  lsr a  
  pha Sendbyte 4 auf Stack
  ldx #$03 Zähler für Loop Byte vorbereiten
loopbyte ldy #$03 Loop Byte, Zähler für Loop Bit vorbereiten
loopbit pla Loop Bit, Sendbyte in akku
  lsr a Bit in Carry schieben
  pha Sendbyte wieder merken
  lda #$38 7*8
  ror a Carry in akku
  lsr a  
  lsr a Bit in Data-Output, Clock=false, Bit0-3=7
  eor #$20 Data-Out invertieren
  sta $dd00 setzen
wait7 bit $dd00 auf Clock=true warten
  bvs wait7 Clock=false, weiter warten
  pla Sendbyte in akku
  lsr a Bit in Carry schieben
  pha Sendbyte wieder merken
  lda #$38 7*8
  ror a Carry in akku
  lsr a  
  lsr a Bit in Data-Output, Clock=false, Bit0-3=7
  eor #$20 Data-Out invertieren
  sta $dd00 setzen
wait8 bit $dd00 auf Clock=false warten
  bvc wait8 Clock=true, weiter warten
  dey Zähler (2 Bit je Loop) –1
  bpl loopbit zu Loop Bit
  pla gesendetes Byte von Stack holen
  dex Zähler Byte –1
  bpl loopbyte zu Loop Byte
  bmi passfree sonst zur Warteschleife

Die Routine "getjoy ml" setzt zunächst das Signal, das den Datensender zum Senden veranlaßt. Die Verzögerungsloops "loop1-5" geben dem Sender Zeit, die Leitungen einzulesen, "loop5" berücksichtigt zusätzlich die Zeit, die der Sender zur Vorbereitung der Bytes braucht. "loopbyte" wird für die Anzahl der zu empfangenden Bytes, also 4mal durchlaufen, "loopbit" für jedes Bit, also 8mal. Der Zähler y in "loopbit" dient außerdem dazu, die Clock-Leitung abwechsend "true" und "false" zu setzen, was für den Sender das Signal ist, das nächste Datenbit auf der Data-Leitung bereitzustellen. Der Loop muß etwas verzögert werden, ehe der Zustand der Data-Leitung erneut abgefragt werden kann, dafür sorgt eine doppelte Folge von pha-pla. Die Ablageadresse "joymem" für die empfangenen Bytes muß für die jeweilige Anwendung festgelegt werden.

getjoy ml

  lda $dd00 Start, Port auslesen
  and #$07 Bit 0-2 isolieren
  ora #$30 Data=true, Clock=true
  sta $dd00 Signal Senden 1 setzen
  ldy #$08 Timeloop für Signal [8*5=40 Takte]
loop1 dey  
  bne loop1  
  and #$17 Data=false, Clock=true
  sta $dd00 Signal Senden 2 setzen
  ldy #$08 Timeloop für Signal
loop2 dey  
  bne loop2  
  and #$07 Data=false, Clock=false
  sta $dd00 Signal Senden 3 setzen
  ldy #$08 Timeloop für Signal
loop3 dey  
  bne loop3  
  ora #$20 Data=true, Clock=false
  sta $dd00 Signal Senden 4 setzen
  ldy #$08 Timeloop für Signal
loop4 dey  
  bne loop4  
  and #$07 Data=false, Clock=false
  sta $dd00 Signal Senden 5 setzen
  ldy #$16 Timeloop bis 1. Bit bereit [22*5=110 Takte]
loop5 dey  
  bne loop5  
  ldx #$03 Zähler für Loop Byte vorbereiten
loopbyte lda #$ff Loop Byte, Byte zum Empfangen
  pha vorbereiten
  ldy #$07 Zähler für Loop Bit vorbereiten
loopbit tya Loop Bit, Bit 0 von Zähler in akku
  lsr a und in Carry
  lda $dd00 Data auslesen
  pha und auf Stack merken
  and #$07 Bits 0-2 isolieren
  bcc skip Zähler gerade, weiter mit Clock=false
  ora #$10 Zähler ungerade, Clock=true
skip sta $dd00 Data=false, Clock nach Bit 0 Zähler setzen
  pla Data-Input von Stack
  asl a und in Carry
  pla Datenbyte holen
  ror a Bit reinschieben
  pha und wieder merken
  pha Verzögerung [14 Takte]
  pla  
  pha  
  pla  
  dey Zähler Bit –1
  bpl loopbit zu Loop Bit
  pla Datenbyte holen
  sta joymem,x und abspeichern (Ablage für empfangene Bytes)
  dex Zähler Bytes –1
  bpl loopbyte zu Loop Byte
  rts fertig

Mögliche Anpassungen:

1.) Andere Datenbytes senden:

Die Vorbereitung der Bytes in Label "prepare" von "sendjoy ml" legt die normalen Joystickports und den Userport für den 4-Spiele-Adapter auf Stack. Das Umschalten von Bit 7 in $DD01 bewirkt, daß erst Joystick 3 und dann Joystick 4 abgefragt wird. Da der Feuerknopf von Joystick 4 in Bit 5 gesetzt ist, wird Bit 5 nach Bit 4 verschoben. Sollen andere Daten gesendet werden, fällt das alles weg. Die zu sendenden Bytes müssen einfach auf den Stack geschoben werden. Die zur Vorbereitung der Bytes nötige Zeit muß im Verzögerungsloop "loop5" der Routine "getjoy ml" berücksichtigt werden, dauert die Vorbereitung der Daten länger, muß der Loop entsprechend öfter durchlaufen werden (5 Takte je Durchlauf), da der Empfänger keinesfalls schneller sein darf als der Sender.

2.) Mehr oder weniger Bytes senden:

Dazu müssen in der Vorbereitung von "sendjoy ml" die entsprechende Anzahl von Bytes auf Stack geschoben und in beiden Routinen die Zähler x von Loop Byte angepaßt werden. Wenn die zu sendenden Bytes hintereinander im Speicher liegen, können beim Senden am Anfang von "loopbyte" die Bytes auch mit Index x ausgelesen und einzeln auf den Stack geschoben werden. Durch das Wegfallen der Vorbereitungszeit kann in "getjoy ml" der Verzögerungsloop "loop5" kürzer dimensioniert werden.

3.) Zwei C64 als Sender:

Der Datensender sendet nach dem Takt des Empfängers - wenn zwei Sender beteiligt sind, senden sie synchron. Damit sich die übermittelten Daten nicht überlagern, muß der eine Sender Leerbytes senden, während der andere seine Datenbytes sendet, und umgekehrt. In der Vorbereitung von "sendbyte ml" kommen beim einen Sender also erst die Datenbytes auf Stack und danach die Leerbytes ($ff), beim zweiten Sender ist es umgekehrt. Der Zähler x für "loopbyte" muß bei Sendern und Empfänger verdoppelt werden, in "getjoy ml" ist zusätzlich die längere Vorbereitungszeit in der Vorgabe y für "loop5" zu berücksichtigen.

 

Nachrichtennetzwerk für bis zu 6 C64

Bei diesem Programm mit dem Namen "sermes" auf Disk handelt es sich um eine Art Demonstrationsbeispiel, wie die Verbindung über den seriellen Bus genutzt werden kann. Es handelt sich um ein Programm in einem Stück, die anderen Files auf Disk werden also nicht benötigt. Zugunsten der Ladezeit wurde es mit pucrunch von Pasi Ojala komprimiert.

Das Programm ermöglicht den Austausch von Textnachrichten zwischen mehreren C64. Die Anzahl der Teilnehmer ist auf 6 Rechner beschränkt, dazu kommt noch eine gemeinsame Floppy für alle. Nachrichten können an alle oder auch ausschließlich an bestimmte Teilnehmer geschickt werden. Gesendete und eingegangene Nachrichten werden zusammen in einer Ablage aufbewahrt. In einem kleinen Texteditor werden neue Nachrichten erstellt, wobei auch die Möglichkeit gegeben ist, auf eingegangene Nachrichten mit Zitat zu antworten. Eine genauere Anleitung befindet sich in der Willkommensnachricht im Programm selbst.

Wer an Einzelheiten zum Protokoll interessiert ist: Der Bus wird prinzipiell von allen Rechnern freigehalten, ist also frei für Floppyzugriffe. Per Einbindung in den IRQ fragen alle Rechner den Bus in Intervallen von 1/60 Sekunden ab. Will ein Rechner senden, setzt er das Signal Data=true für eine ausreichende Zeitspanne, um registriert zu werden, und setzt daraufhin eine charakteristische Abfolge von Buszuständen. Abweichende Buszustände, wie sie z.B. bei Floppyzugriff auftreten, werden von den Teilnehmern folglich als Störung erkannt und ausgeschieden. Nach der Einleitungssequenz sendet der Sender zweimal seine Identifikationsnummer (ID), die nach der Reihenfolge der Anmeldungen im Netzwerk vergeben wird. Bis zu dieser Phase kann ein zeitgleiches Senden mehrerer Teilnehmer erkannt und fehlerfrei behandelt werden. Danach enthält die Sendung 3 Befehlsbytes, die Art der Sendung und Empfängerkreis bestimmen. Als nächstes erfolgt eine interaktive Abfrage der vorhandenen Teilnehmer am Bus, die nach dem Takt des Senders mit ihrer ID antworten. (Ein Neuankömmling meldet sich mit ID=0 an, und kann aus dieser Antwort erkennen, welche ID im Netzwerk noch frei ist.) Dann folgt die eigentliche Textsendung. ID und Befehlsbytes sind Teil eines bescheidenen Protokolls auf zweiter Ebene. Sie unterscheiden Neuanmeldung (ID=0, Zufallsbytes), Usernamenmeldung (muß verifiziert werden), Netzdatenbestand (alle Usernamen/Systemzeit), Textnachricht (zugeordnet an alle/bestimmte Empfänger, mit Option "normal"/"eilt") und ein temporäres Sendeverbot für alle, mit der ein Teilnehmer sich den ungestörten Diskzugriff sichern kann.

Mögliche weitere Anwendungen für die C64-C64-Bus-Verbindung wären Rechennetzwerke für zeitaufwendige Berechnungen oder ein C64-C64-Assembler: der eine C64 hält den Quellcode, der andere empfängt den Objektcode und erlaubt einen raschen Testlauf. Unter der Bedingung, daß der VIC vorübergehend abgeschaltet wird, ließen sich für solche Prokekte duchaus auch floppyspeeder-ähnliche Übertragungsraten erzielen. Mein ohnehin nur experimentelles Interesse am seriellen Bus ist aber weitgehend erschöpft, die Verwirklichung dieser Möglichkeiten ist nicht geplant.


Kontakt: kottira@webnet.at