CPC 464 Timeportal

Diese Seite dokumentiert den Versuch, einen CPC 464 von 1984 mit einem modernen PC zu verbinden.

Inhalt:

Einleitung

Der Schneider CPC 464 ist ein Relikt aus den 1980er Jahren und stand als solches in meinem Elternhaus. Dazu muss man sagen, dass er zu diesem Zeitpunkt eigentlich auch schon ins Museum gehört hätte, aber er war mein erster Kontakt mit Computern und so verbrachte ich einen nicht unwesentlichen Teil meiner Kindheit und Jugend mit diesem Gerät. In erster Linie natürlich mit den verfügbaren Spielen.

Vor einiger Zeit hatte ich die Idee, auf einem dieser alten Schätzchen eine Webseite zu hosten. Warum? Spaß am experimentieren!

Dabei wurde recht schnell klar, dass es zwar eine Reihe von Informationen im Netz gibt, aber das Wenigste davon wirklich vom ersten bis zum letzten Schritt reicht. Diese Seite unternimmt den Versuch, diese Lücke zu schließen.

Der CPC ist ein 8-bit-Rechner und bietet nur relativ wenige Möglichkeiten ihn mit moderner Hardware kommunizieren zu lassen. Eine dieser Möglichkeiten ist der Druckerport, welcher aber in der ersten Version des CPC (um die es hier geht) ein paar Probleme mit sich bringt. Dazu später mehr. Immerhin gibt es aber eine Schnittstelle, welche im Prinzip und mit ein wenig Bastelei mit moderner Hardware kommunizieren kann.

Der Schneider CPC464 mit 'Green Monitor'
Der Schneider CPC464 mit "Green Monitor"

Das erste Ziel dieses Projektes ist es, vom CPC bereitgestellte Texte zum PC zu übertragen und umgekehrt. Es wird eine Python-Erweiterung in C geschrieben, welche den Datentransfer vom CPC zum PC (und umgekehrt) erleichtert.

Natürlich ist alles was hier beschrieben wird nur auf eigene Gefahr nachzubauen. Es wird keinerlei Gewähr oder Haftung für Hardwareschäden, Verletzungen oder die Funktionsfähigkeit der beschrieben Hard- und Software übernommen. Generell sollte man beim Löten immer vorsichtig sein und sich der Gefahr bewusst sein, dass man den PC bzw. CPC beschädigen oder sogar zerstören kann.

Vorbereitungen

Zunächst wird ein wenig Hardware, Werkzeug und Material gebraucht:

Der LPT Port am PC

Der Anschluss des Parallelports besteht aus 25 Leitungen. Wird sein Stecker von vorne betrachtet, so beginnt die Zählung in der oberen Reihe von rechts bei eins und endet links bei 13. In der unteren Reihe wird ebenfalls von rechts nach links und von 14 bis 25 gezählt.

Eine SUB-D 25-Buchse mit vier eingezeichneten Anschlussnummern zur Orientierung
Eine SUB-D 25-Buchse mit vier eingezeichneten Anschlussnummern zur Orientierung

Der Parallelport hat drei Register: Daten-, Status- und Kontrollregister. Das Datenregister hat die Speicheradresse 0x378 und ist für die Pinne 2 bis 9 zuständig. Über diese Pinne kann ein Byte parallel (also auf einmal) entweder gelesen oder geschrieben werden. Ob das Datenregister gelesen oder Daten hineingeschrieben werden sollen, entscheidet ein Bit im Kontrollregister. Dazu später mehr. Die Adresse des Kontrollregisters ist 0x37A. Das Statusregister hat die Speicheradresse 0x379.

PC vorbereiten

Um die grundsätzliche Funktionsfähigkeit des Parallelports am PC zu testen und später das Verhalten der Schnittstelle zu visualisieren, bietet es sich an, für den PC eine Sub-D 25 Plugbox zu verdrahten. Mit dieser lassen sich leicht LED's an den Parallelport des PC's anschließen und ansteuern.

Die Plugbox welche ich verwendet habe ist "Male" und hat 25 Anschlusspinne sowie 25 Anschlüsse an welche leicht die Dioden und Widerstände angeschlossen werden können.

Die Datenpinne sind die Nummern 2 bis 9. Für die ersten Versuche wird die Plugbox also an den Anschlüssen 2 bis 9 mit Dioden bestückt. Bei jeder Diode gilt: Das lange Beinchen kommt an den Datenpin (also die Pinne zwei bis neun) und an das kurze wird ein Vorwiderstand (1 kOhm) angelötet welcher dann an die Anschlüsse 18 bis 25 (jeweils einer) angeklemmt wird. Die letztgenannten Anschlüsse liegen an Masse an. Sollten mehr Dioden (z.B. um auch andere Register zu visualisieren) benötigt werden, so können sich auch mehrere Dioden eine Masse teilen.

Die fertige Plugbox mit 8 LEDs
Die fertige Plugbox mit acht LEDs

Für das ganze Projekt werden die Pinne des Datenregisters sowie Pin 1 (Kontrollregister) und Pin 10 (Statusregister) benötigt. Pin 1 ist wird als Rückkanal zum CPC benutzt werden. Da das Datenregister des CPC nur beschrieben werden, nicht aber gelesen werden kann, ist dies die einzige Möglichkeit Daten in Richtung des CPC zu senden.

Sobald alle Dioden und Widerstände angeschlossen sind, kann die Plugbox an den Parallelport des PCs angeschlossen werden. Optimalerweise sollte man ein entsprechendes Kabel zur Hand haben, damit die Plugbox besser in Sichtweite positioniert werden kann.

Nun ist es an der Zeit sich der Software zu widmen und einen ersten Versuch zu unternehmen, die Dioden nach dem eigenen Willen leuchten zu lassen. Dazu wird das folgende kleine C-Programm geschrieben und kompiliert:

#include <sys/io.h>

int main(void) {

    /* Open data and controll port */
    ioperm(0x378,1,1);
    ioperm(0x37A,1,1);

    /* Make sure to have set the data port to write mode */
    outb(0, 0x37A);

    /* Write a full byte to the data port */
    outb(255, 0x378);
    return 0;
}
    

Das Kompilieren und ausführen sollte so funktionieren:

$ gcc -O example.c -o example
$ ./example
    

Da nur root auf den Speicher des Parallelports zugreifen darf, muss das Programm mit sudo (oder eben als root) gestartet werden. Wenn alles geklappt hat, dann strahlen nun alle LED's:

Die Plugbox mit aktivierten LEDs
Die Plugbox mit aktivierten LED's

Natürlich kann man auch nur einzelne LEDs an und ausschalten. Dazu ist es erforderlich, dass man versteht was hier passiert. Das Datenregister ist acht Bit -also ein Byte- lang. Ist ein Bit davon gesetzt, so leuchtet die entprechende LED. Im Beispiel wurde der Dezimalwert 255 in das Register geschrieben. Dieses entspricht im Binärsystem 11111111. Also acht Einsen. Wird eine dezimale 1 in das Register geschrieben, so entspricht dies im Binärsystem ebenfalls 1 (die anderen sieben Stellen kann man sich als mit Nullen gefüllt vorstellen).

Eine dezimale 2 entspricht also 00000010 und eine 3 wird zu 00000011. Werden diese Zahlen in das Register geschrieben, so wird bei der 2 die LED am Pin 2 leuchten und bei einer 3 werden die LED's an Pin 1 und 2 leuchten. Die LED's und der Datenport sind also eine vollständige Abbildung des Binärsystems im Bereich von 0 bis 255.

Um nun eine bestimmte einzelne LED zum Leuchten zu bringen müssen also die Binärwerte, welche jeweils nur an der entsprechenden Stelle eine 1 haben gefunden werden. Dies sind praktischerweise natürlich immer die Werte welche das Doppelte des vorherigen sind. Also im Dezimalsystem: 1, 2, 4, 8, 16, 32, 64, 128. Wenn diese Zahlen in einer Schleife an das Datenregister übergeben werden, so wird aus den LED's ein Lauflicht:

#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>

int main(void) {

    /* Open data and controll port */
    ioperm(0x378,1,1);
    ioperm(0x37A,1,1);

    /* Make sure to have set controll port to write mode */
    outb(0, 0x37A);

    /* We need a pause to see the effect */
    int pause = 90000;
    int pins[8] = {1, 2, 4, 8, 16, 32, 64, 128};

    /* A while loop to run forever */
    int i;
    while(1)
    {
        for(i = 0; i <= 7; i++)
        {
            outb(pins[i], 0x378);
            usleep(pause); /* To slow it down */
        }
        outb(0, 0x378);
    }
    return 0;
}
    

Der Printer Port am CPC

Der Printer Port am CPC besteht aus 34 Ätzungen auf der Hauptplatine. Eine Besonderheit ist, dass er eigentlich aus 36 bestehen müsste, man aber auf die Anschlüsse 18 und 36 verzichtet hat. Bei der Nummerierung der Anschlüsse geht diese Beschreibung aber davon aus, dass es 36 sind.

Die datenführenden Anschlüsse sind die Nummern zwei bis acht. Es gibt also nur sieben, womit nur reines ASCII übertragen werden kann. Außerdem sind sie (im Gegensatz zu den Datenpinnen am PC) nur schreibend zu verwenden; sie können nicht ausgelesen werden.

An Anschluss elf liegt das BUSY-Signal. Dieser Anschluss kann ausgelesen werden und bildet damit also den Kanal vom PC zum CPC. Umgekehrt können die Datenanschlüsse vollständig an den PC gesendet und von diesem auch ausgelesen werden.

Die folgende Darstellung zeigt die Anschlüsse des Printer Ports in der Rückansicht des CPC. Zwischen den Anschlüssen vier und fünf bzw. 21 und 22 ist die Platine als Verpolungsschutz eingekerbt.

|(18)| 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 |
----------------------------------------------------------------------|--------------------
|(36)| 35 | 34 | 33 | 32 | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 |

CPC vorbereiten

Damit die Verbindung hergestellt werden kann, muss der CPC vorbereitet werden. Hier kommt nun ggf. der Lötkolben zum Einsatz. Es empfiehlt sich, einen Edge Connector mit 34 Anschlüssen und ein entsprechendes Flachkabel zu verwenden. Das Kabel an diesem an zu schließen ist dank Schneid-Klemm-Technik relativ einfach. Am anderen Ende des Kabels wird ein Sub-D 25-Anschluss verlötet.

Der Edge Connector mit angeklemmtem Kabel
Der Edge Connector mit angeklemmtem Kabel
Der Edge Connector am CPC
Der Edge Connector am CPC

Die Anschlüsse des Statusregisters (also die Nummern 2 bis 8) werden auf ihren entsprechenden Gegenstücken am Sub-D 25-Anschluss angelötet. Hierbei ist zu beachten, dass bei einem Edge Connector die Zählweise der Anschlüsse nicht der Zählweise der Platinenanschlüsse entspricht. Die Zählung geht so vor, dass in der oberen Reihe alle geraden und in der unteren alle ungeraden Nummern liegen. Also etwa so:

| 02 | 04 | 06 | 08 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 |
--------------------------------------------------------------------------------------
| 01 | 03 | 05 | 07 | 09 | 11 | 13 | 15 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 31 | 33 |

Es muss also beachtet werden, dass die nebeneinander liegenden Kabel nicht benachbarte Kabel im Sinne des Platinenanschluss sind. Die folgende Tabelle zeigt wie die Anschlüsse des CPC mit dem Sub-D 25-Anschluss verbunden werden müssen:

# CPC Funktion CPC # Sub-D 25 Funktion PC
1 Strobe 10 ACK
2 Datenbit 0 2 Datenbit 0
3 Datenbit 1 3 Datenbit 1
4 Datenbit 2 4 Datenbit 2
5 Datenbit 3 5 Datenbit 3
6 Datenbit 4 6 Datenbit 4
7 Datenbit 5 7 Datenbit 5
8 Datenbit 6 8 Datenbit 6
9 GND 9 Datenbit 7
10 N.c. - -
11 Busy <- 1 Strobe
12 N.c. - -
13 N.c. - -
14 GND - -
15 N.c. - -
16 N.c. GND -
17 N.c. - -
18 Existiert nicht - -
19 GND 18 GND
20 GND 19 GND
21 GND 20 GND
22 GND 21 GND
23 GND 22 GND
24 GND 23 GND
25 GND 24 GND
26 GND 25 GND
27 N.c. - -
28 GND 25 GND
29 N.c. - -
30 N.c. - -
31 N.c. - -
32 N.c. - -
33 N.c. - -
34 N.c. - -
35 N.c. - -
36 Existiert nicht - -
Der verlötete Anschluss
Der verlötete Anschluss

Auf dem Foto oben kann man die beiden über Kreuz gelegten Kabel 1 (auf 10) und 11 (auf 1) erkennen. Somit wird das Strobe-Signal (Anschluss 1) des CPC später im PC im Statusregister ankommen, was das auslesen des Signals gegenüber einem Anschluss im Kontrollregister deutlich vereinfacht, da dieses u.a. dazu verwendet wird, das Datenregister in den Lese- bzw. Schreibmodus zu setzen.

Das ganze Konstrukt
Das ganze Konstrukt

Getested weden kann das ganze mit der Plugbox welche schon beim PC zum Einsatz kam. Sie muss hierfür nicht ggf. etwas angepasst werden, da die GND-Anschlüsse nicht alle zu denen des PC passen müssen. Dann kann auch hier ein Lauflicht programmiert werden:

10 DIM a[6]
20 a(0)=1
30 a(1)=2
40 a(2)=4
50 a(3)=8
60 a(4)=16
70 a(5)=32
80 a(6)=64
90 FOR i=0 TO 6 STEP 1
100 OUT &EF, a(i)
110 t=TIME:WHILE TIME < t + 100:WEND
120 NEXT i
130 GOTO 90

Senden vom CPC zum PC

Um einen einzelnen Buchstaben über den Parallelport an den PC zu senden, muss der binäre Wert des entsprechenden ASCII-Zeichens in das Register des Ports geschoben werden. Dies geschieht mit dem folgenden Aufruf:

OUT &EF, ASC("h")

Damit der PC etwas empfangen kann, muss der folgende C-Code in einer Datei (example.c) gespeichert, kompiliert und ausgeführt werden:

#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>

int main(void) {

    /* Open data and controll port */
    ioperm(0x378,1,1);
    ioperm(0x37A,1,1);

    /* Setting control register to read mode */
    outb(255, 0x37A);

    /* Reading the data port in a loop forever */
    while (1){
        printf("Read from data port: %c\n", inb(0x378));

        /* To slow things down a bit */
        usleep(100000);
    };

    return 0;
}
    

Nicht vergessen, dass nur root auf den Parallelport zugreifen darf. Also die Datei ggf. mit sudo oder direkt als root ausführen. Wenn alles geklappt hat, dann sollte nun folgendes zu sehen sein:

Read from data port port: h
Read from data port port: h
...
    

Senden vom PC zum CPC

Es kann nur ein Bit übertragen werden. Um auf dem CPC die Änderungen zu sehen, kann das folgende Programm laufen:

10 a=INP(&F500)
20 PRINT "Dec", "Bin", "State"
30 PRINT a, BIN$(a, 7), MID$(BIN$(a, 7), 1, 1)
40 t=TIME:WHILE TIME < t + 200:WEND
50 GOTO 10

Hier wird zunächst der Eingangsport an Adresse &F500 in die Variable "a" gelegt. In Zeile 30 werden die drei Werte aus dem Wert in "a" generiert. Hier ist zu beachten, dass für den State ein bisschen getrixt wird: Es wird per MID$ nur die erste Stelle des vorher mit BIN$ auf 7 Stellen festgelegten Strings (BIN$ macht aus einer Dezimalzahl einen String welcher den binären Wert darstellt) ausgegeben. Das heisst, wir haben hier keine echte Zahl sondern ein Char, das eine 0 abbildet. Für diesen Anwendungsfall reicht das aber vollkomment aus.

Zeile 40 ist nur eine Verzögerung, damit die Routine nicht mit maximaler Geschwindigkeit läuft sondern etwas ausgebremst wird. Zeile 50 startet einfach alles wieder von Anfang.

Auf dem PC muss folgender Code kompiliert und dann ausgeführt werden:

#include <stdlib.h>
#include <stdio.h>
#include <sys/io.h>

int main(int argc, char *argv[]) {

    ioperm(0x37A,1,1);

    int arg = atoi(argv[1]);
    printf("Sending %d", arg);

    outb(arg, 0x37A);

    return 0;
}
    

Die main-Funktion nimmt den ersten Parameter aus der Kommandozeile und sendet den Wert an den Kontrollport. Dem Programm kann also beim Aufruf eine Zahl übergeben werden. Hier bieten sich 0 und 1 an, da so im CPC der gewünschte Effekt eintritt: Der ausgelesene Pin 11 ändert seinen Zustand entsprechend dem gesendeten Wert.

./example 0
./example 1
...
    

Die Ausgabe auf dem CPC sieht dann in etwa so aus:

Dec Bin State
122 1111010 1
Dec Bin State
122 1111010 1
Dec Bin State
58 0111010 0
Dec Bin State
58 0111010 0

Dem aufmerksamen Leser wird aufgefallen sein, dass der Beispielaufruf und die Beispielausgabe scheinbar in verkehrter Reihenfolge hier angezeigt werden. Schließlich wird doch zuerst eine 0 und dann eine 1 gesendet während der CPC es genau anders herum anzeigt. Dies ist allerdings kein Fehler: Der Pin 1 des Parallelports am PC ist invertiert. Wird eine 1 im Speicher gesetzt so, liegt am Pin keine Spannung an und umgekehrt. Das mag spannende technische Gründe haben, aber hier sollte man es einfach nur beachten oder zumindest zur Kenntnis nehmen.

Python-Erweiterung

Eine Python-Erweiterung bietet sich an um einfacher auf den Port zugreifen zu können. Die im folgenden beschriebene Erweiterung besteht aus einem Modul pyparport welches eine Klasse PyParport enthält. Der Quellcode der Erweiterung sowie der der später folgenden Beispielprogramme, kann hier heruntergeladen werden (die Erweiterung liegt im Verzeichnis "C").

Diese wiederum hat die Methoden data (Datenregister), control (Kontrollregister) und status (Statusregister). Jede dieser Methoden bringt die Methoden read() und write().write() muss der Wert der geschrieben werden soll als Dezimalzahl übergeben werden.

Die Klassen des Moduls
Die Klassen des Moduls

Der folgende C-Code stellt den hardwarenahen Teil der Python-Erweiterung dar:

#include 
#include 
#include 
#include 

static PyObject* PortError;

static PyObject* readport(PyObject* self, PyObject *args)
{

    /* Open registers */
    ioperm(0x378,1,1);
    ioperm(0x379,1,1);
    ioperm(0x37A,1,1);

    char *reg;
    int addr;

    if (!PyArg_ParseTuple(args, "si", ®, &addr))
    {
        return NULL;
    }

    PyArg_ParseTuple(args, "si", ®, &addr);

    if (!strcmp(reg, "d"))
    {
        /* Set dataport to read mode */
        outb(255, addr+2);
        /* Read the port */
        return Py_BuildValue("i", inb(addr));
    }
    else if (!strcmp(reg, "s") | !strcmp(reg, "c"))
    {
        /* Read the port */
        return Py_BuildValue("i", inb(addr));
    }
    else
    {
        PyErr_SetString(PortError, "Please choose a valid register: d(ata), c(ontroll) or s(tatus)");
        return NULL;
    }
}

static PyObject* writeport(PyObject* self, PyObject *args)
{
    /* Open registers */
    ioperm(0x378,1,1);
    ioperm(0x379,1,1);
    ioperm(0x37A,1,1);

    int val;
    char *reg;
    int addr;

    if (!PyArg_ParseTuple(args, "isi", &val, ®, &addr))
    {
        return NULL;
    }
    PyArg_ParseTuple(args, "isi", &val, ®, &addr);

    if (!strcmp(reg, "d"))
    {
        /* Set dataport to write mode */
        outb(0, addr+2);
        /* Set the port */
        outb(val, addr);
    }
    else if (!strcmp(reg, "s") | !strcmp(reg, "c"))
    {
        /* Set the port */
        outb(val, addr);
    }
    else
    {
        PyErr_SetString(PortError, "Please choose a valid register: d(ata), c(ontroll) or s(tatus)");
        return NULL;
    }
    Py_RETURN_NONE;
}


static PyMethodDef pyparport_funcs[] = {
    {"read",    (PyCFunction)readport,  METH_VARARGS},
    {"write",   (PyCFunction)writeport, METH_VARARGS},
    {NULL}
};

#if PY_MAJOR_VERSION >= 3 // Python3 compatibilty
static struct PyModuleDef _interface =
{
    PyModuleDef_HEAD_INIT,
        "_interface",
        "Python parallel port object",
        -1,
        pyparport_funcs
};

PyMODINIT_FUNC PyInit__interface(void) {
    PyObject *module;
    module = PyModule_Create(&_interface);

    PortError = PyErr_NewException("pyparport.PortError", NULL, NULL);
    Py_INCREF(PortError);
    PyModule_AddObject(module, "PortError", PortError);

    return module;
}

#else // Python2 compatibilty
void init_interface(void)
{
    PyObject *module;
    module = Py_InitModule3("_interface", pyparport_funcs, "Python parallel port object");

    PortError = PyErr_NewException("pyparport.PortError", NULL, NULL);
    Py_INCREF(PortError);
    PyModule_AddObject(module, "PortError", PortError);
}
#endif
    

Der Code wird in einer Datei pyparport.c gespeichert, welche dann ihrerseits in ein Unterverzeichnis _interface gelegt wird. Neben diesem Verzeichnis wird ein weiteres Verzeichnig pyparport gelegt, welches eine Datei __init__.py mit folgendem Inhalt enthält:

#!/usr/bin/env python
# coding: utf-8

import _interface

PortError = _interface.PortError


class Port(object):
    """ Abstraction layer for a more comfortable use """

    def __init__(self, port, addr):
        self.port = port
        self.addr = addr

    def read(self):
        return _interface.read(self.port, self.addr)

    def write(self, value):
        return _interface.write(value, self.port, self.addr)


class PyParport(object):
    """ The main class which implements the interface to the port """

    def __init__(self, base_addres=0x378):
        self.data_address = base_addres
        self.status_address = base_addres + 1
        self.control_address = base_addres + 2

        self.data = Port("d", self.data_address)
        self.status = Port("s", self.status_address)
        self.control = Port("c", self.control_address)

    

Außerdem wird noch eine setup.py im Hauptverzeichnis des Projektes benötigt, mit deren Hilfe die Erweiterung kompiliert und in die Pythonumgebung installiert wird. Es bietet sich an, die Erweiterung in ein Virtualenv zu installieren, da es bei einer systemweiten Installation nur sehr umständlich möglich ist, sie wieder zu entfernen. Die Datei hat den folgenden Inhalt:

from setuptools import setup, Extension

extension = Extension("_interface", sources=["_interface/pyparport.c"])

setup(name="pyparport",
      version="0.6",
      description="This module provides the possibility to connect to parallel ports from Python.",
      license='GPLv3',
      packages=['_interface', "pyparport"],
      author='Christian Kokoska',
      author_email='christian@softcreate.de',
      ext_modules=[extension])
    

Somit sollte das Projekt folgendermaßen aufgebaut sein:

$ cd pyparport
$ tree .
.
├── _interface
│   └── pyparport.c
├── pyparport
│   └── __init__.py
└── setup.py
    

Die Datei sollte in das Virtualenv (pythonenv) kopiert werden. Mit dem folgenden Aufruf wird die Erweiterung dann in selbiges installiert:

$ bin/python setup.py install
    

Die Benutzung wird durch das folgendes Codebeispiel erläutert:

from pyparport import PyParport

parport = PyParport()

# Show the data of the data register
parport.data.read()

# Show the data of the control register
parport.control.read()

# Show the data of the status register
parport.status.read()

# To write 0 to the data register
parport.data.write(0)

# To write a 255 to the data register
parport.data.write(255)
    

Verwendungsbeipiele

Im Folgenden werden einige Beispiele zur Verwendung aufgezeigt. Der Nutzen der einzelnen Anwendungen liegt hauptsächlich im Nachweis der Machbarbeit. Der Code der Beispiele kann hier heruntergeladen werden.

CPC als Sender von Eingaben

10 OUT &EF, 127
20 INPUT "$: ", content$
30 sig=1
40 IF MID$(BIN$(INP(&F500),7),1,1)=cur$ THEN GOTO 40 ELSE GOTO 50
50 PRINT "Sending.. "+content$
60 FOR c=1 TO LEN(content$) STEP 1
70 IF sig=1 THEN GOTO 80 ELSE GOTO 110
80 sig=0
90 OUT &EF, ASC(MID$(content$,c,1))+128
100 GOTO 130
110 sig=1
120 OUT &EF, ASC(MID$(content$,c,1))
130 NEXT c
140 PRINT "Done"
150 OUT &EF, 0
160 OUT &EF, 129
170 OUT &EF, 1
180 GOTO 20
#!/bin/env python

from time import sleep

from pyparport import PyParport


parport = PyParport()


while True:
    end = False
    content = ""
    cur = parport.status.read()

    while not end:
        if parport.status.read() == cur:
            continue
        else:
            cur = parport.status.read()
            if not parport.data.read() == 1:
                content += str(unichr(parport.data.read()))
            else:
                end = True

    print content
    sleep(0.001)
    

CPC als Server

Der CPC kann mit der nachfolgenden Routine Daten auf Anforderung ausliefern. Man könnte also sagen, dass er auf einen Request mit einem Response reagiert. Somit lässt er sich als Server verwenden.
10 OUT &EF, 127
20 content$="<html><body><h1>CPC als Server</h1>Diese Seite wird von einem CPC464 ausgeliefert.</body></html>"
30 sig=1
40 cur$=MID$(BIN$(INP(&F500),7),1,1)
50 IF MID$(BIN$(INP(&F500),7),1,1)=cur$ THEN GOTO 50 ELSE GOTO 60
60 PRINT "Incoming request"
70 FOR c=1 TO LEN(content$) STEP 1
80 IF sig=1 THEN GOTO 90 ELSE GOTO 120
90 sig=0
100 OUT &EF, ASC(MID$(content$,c,1))+128
110 GOTO 140
120 sig=1
130 OUT &EF, ASC(MID$(content$,c,1))
140 NEXT c
150 PRINT "Resetting"
160 OUT &EF, 0
170 OUT &EF, 129
180 OUT &EF, 1
190 GOTO 30
Eine kleine Application mit Flask stellt dann die vom CPC ausgelieferte Seite im Netz bereit:
#!/usr/bin/env python

import os
from time import sleep

from flask import abort, Flask, Response
from pyparport import PyParport


parport = PyParport()


app = Flask(__name__)

app.config["PROPAGATE_EXCEPTIONS"] = True


class CpcLocked(Exception):
    pass


lockfilepath = "/tmp/cpc464.lock"


class EnsureFileLock:
    """Creates a lock file on script initialisation and removes it at it's end."""

    def __enter__(self):
        if os.path.isfile(lockfilepath):
            raise CpcLocked("The CPC464 is locked or works on another request.")
        open(lockfilepath, "a").close()

    def __exit__(self, type, value, traceback):
        os.remove(lockfilepath)


def change_state():
    if parport.control.read() == 192:
        parport.control.write(255)
    elif parport.control.read() == 255:
        parport.control.write(0)

@app.route('/')
def index():
    try:
        with EnsureFileLock():
            change_state()

            end = False
            content = ""
            cur = parport.status.read()

            while not end:
                if parport.status.read() == cur:
                    continue
                else:
                    cur = parport.status.read()
                    if not parport.data.read() == 1:
                        content += str(unichr(parport.data.read()))
                    else:
                        end = True
            return Response(content)

    except CpcLocked as e:
        return Response(e)
    

Browser für den CPC

Es lässt sich auch eine Art Webbrowser für den CPC schreiben, bei dem der PC quasi als Proxy dient.
10 MODE 2
20 OUT &EF, 127
30 INPUT "$: ", content$
40 sig=1
50 IF MID$(BIN$(INP(&F500),7),1,1)=cur$ THEN GOTO 50 ELSE GOTO 60
60 FOR c=1 TO LEN(content$) STEP 1
70 IF sig=1 THEN GOTO 80 ELSE GOTO 110
80 sig=0
90 OUT &EF, ASC(MID$(content$,c,1))+128
100 GOTO 130
110 sig=1
120 OUT &EF, ASC(MID$(content$,c,1))
130 NEXT c
140 OUT &EF, 0
150 OUT &EF, 129
160 t=TIME:WHILE TIME<t+300:WEND
170 linecount=0
180 resp$=""
190 a$=""
200 sig=0
210 FOR i=1 TO 7 STEP 1
220 t=TIME:WHILE TIME<t+1:WEND
230 IF sig=0 THEN 240 ELSE GOTO 270
240 sig=1
250 OUT &EF, 129
260 GOTO 290
270 sig=0
280 OUT &EF, 0
290 IF INP(&F500)=122 THEN GOTO 300 ELSE GOTO 320
300 a$=a$+"0"
310 GOTO 330
320 a$=a$+"1"
330 NEXT i
340 res$=CHR$(VAL("&x"+a$))
350 OUT &EF, 0
360 IF res$=CHR$(4) THEN GOTO 460
370 resp$=resp$+res$
380 IF LEN(resp$)>74 THEN GOTO 390 ELSE GOTO 450
390 PRINT resp$
400 LINECOUNT=LINECOUNT+1
410 IF LINECOUNT=22 THEN GOTO 420 ELSE 440
420 INPUT "-- continue --", CON$
430 LINECOUNT=0
440 resp$=""
450 GOTO 190
460 PRINT resp$
470 GOTO 20
#! coding: utf-8
#!/bin/env python

import binascii
import os
import re
from time import sleep

from pyparport import PyParport


parport = PyParport()


def receive():
    end = False
    url = ""
    cur = parport.status.read()

    while not end:
        if parport.status.read() == cur:
            continue
        else:
            cur = parport.status.read()
            if not parport.data.read() == 1:
                url += str(unichr(parport.data.read()))
            else:
                end = True
    parport.control.write(0)
    send(url)


def send(url):
    print "CPC asks for URL: {}".format(url)
    url = url.replace("\0", "")
    try:
        content = os.popen("lynx --dump --nolist " + url).read()
    except TypeError as e:
        print e
        content = "404"

    content = re.sub("\s+", " ", content
                .replace("\n", " ")
                .replace("\xf6", "oe")
                .replace("\xd6", "OE")
                .replace("\xe4", "ae")
                .replace("\xc4", "AE")
                .replace("\xfc", "ue")
                .replace("\xdc", "UE")
                .replace("\xdf", "ss"))

    content = "00000000" + bin(int(binascii.hexlify(content), 16))[2:]
    content += "00000100"

    cur = parport.status.read()
    while cur != 63:
        cur = parport.status.read()

    for i in content:
        while cur == parport.status.read():
            continue
        if i == "0":
            parport.control.write(0)
        elif i == "1":
            parport.control.write(255)
        cur = parport.status.read()
    receive()

receive()
    

Nachwort

Auch wenn es noch Verbesserungspotential gibt und die eine oder andere Routine noch Schwachstellen hat, so ist es doch möglich einen Computer, der mehr als 30 Jahre alt ist, mit moderner Hardware kommunizieren zu lassen und ihn sogar auf die eine oder andere Weise zu einem Teil des Internets zu machen.

Ich für meinen Teil habe gelernt, dass selbst in einem so alten Rechner sehr viel Potential steckt und es sicher noch viel mehr drin. Verbesserungsvorschläge und Anregungen sind immer herzlich willkommen!

Nicht zuletzt möchte ich mich bei meinem Vater für die Hilfe bei diesem Projekt bedanken. Ich habe die gemeinsamen Stunden mit Lötkolben, Dioden, Kabeln und Steckern sehr genossen.

Rechtliches

Impressum - Datenschutz

Creative Commons Lizenzvertrag Dieses Werk fällt unter diese Lizenz: Creative Commons by-nc-sa 4.0