Hewlett-Packard an ST: „Bitte kommen!“ - Datenübertragung vom HP-Taschenrechner zum Atari ST

Der HP-28S sowie fünf weitere wissenschaftliche Taschenrechner von Hewlett-Packard verfügen über einen Infrarotausgang zur Übertragung von Daten an einen Drucker. Leider kann sich nicht jeder den passenden Thermodrucker HP-82240 leisten, doch wer einen Atari mit Drucker besitzt, kann mit der hier vorgestellten Schaltung für 10 DM seine Anlage in einen HP-Drucker verwandeln, der Texte, Zahlen und Grafiken nicht nur ausdrucken kann...

Infrarote Datenübertragung

Jeder kennt die Datenübertragung durch Infrarotlicht von der Fernsteuerung eines Fernsehers; und die Methode von Hewlett-Packard, auf diese Weise Daten vom Taschenrechner zum Drucker zu übertragen, hat sicher Vorteile gegenüber verschleißenden Steckverbindern, über die zerstörend wirkende elektrostatische Entladungen stattfinden könnten. Eine Leuchtdiode im Taschenrechner sendet die Daten in Form von unsichtbaren infraroten Lichtpulsen Bit für Bit an den Drucker, und dieser rekonstruiert daraus die zu druckenden Zeichen. Die abgedruckte Software macht das gleiche, doch dazu muß erst einmal eine Schaltung her, mit der der Atari erkennen kann, was der Taschenrechner spricht: Licht oder kein Licht.

Funktionsweise der Schaltung

Das Bauteil, das das Infrarotlicht „sehen“ kann, ist ein Fototransistor des Typs BPW40. Er läßt einen zur Lichtintensität proportionalen Strom fließen, und am Widerstand R1 fällt eine dazu proportionale Spannung ab. Diese wird zunächst vom Operationsverstärker OP1 verstärkt, R2 und R5 legen den Verstärkungsfaktor dabei auf 34 fest. Die vier OPs in der Schaltung sind übrigens zusammen in einem 14poligen IC untergebracht, es ist der preiswerte Standardtyp LM324.

Am Ausgang von OP1 steht nun eine Spannung zur Verfügung, die die Helligkeit widerspiegelt. Um dieses analoge Signal in ein digitales zu verwandeln, das der Computer erkennen kann, vergleicht der Komparator OP4 den Ist-Wert der Helligkeit mit einem Referenz wert, der an seinem invertierenden Eingang anliegt. Ist die Spannung höher als der Vergleichswert, liefert der Komparator am Ausgang 12 Volt, logisch 1, und sonst -12V, logisch 0.

Ein Vergleich mit einem festen Wert wäre allerdings nicht sinnvoll. Hierbei könnte es passieren, daß durch die Grundhelligkeit im Zimmer der Schwellenwert permanent überschritten wird, egal, ob der Taschenrechner nun sendet oder nicht. Verglichen wird deshalb mit einem langfristigen Mittelwert der Spannung von OPI, da dieser Mittelwert der Grundhelligkeit im Zimmer entspricht.

Um diesen Referenzwert zu erzeugen, wird das Signal von OP1 zunächst über OP2 geführt, der als Spannungsfolger geschaltet ist und nichts weiter macht, als am Ausgang die gleiche Spannung zu liefern, die am Eingang anliegt. Im Gegensatz zur direkten Verbindung, dem Draht, findet beim Spannungsfolger oder Impedanzwandler aber keine Rückwirkung der ausgangsseitigen Signale auf den Eingang statt. Das Signal passiert nun den Tiefpaßfilter, den R4 und C1 bilden. Hochfrequente Signale, wie die Impulse des Taschenrechners, werden dabei stark gedämpft. und was übrig bleibt, ist ein Spannungswert, der zur Grundhelligkeit proportional ist.

Bild 2: Der Schaltplan des HP-Empfängers

Würde der Ist-Wert nun mit diesem Referenzwert verglichen, während keine Lichtpulse vom Taschenrechner kommen, so würden an den Eingängen von OP4 annähernd gleiche Spannungen anliegen. und schon bei leichten Schwankungen könnte das Ausgangssignal kippen. Daher wird mit einem Wert verglichen, der etwas über dem Wert der Grundhelligkeit liegt, was mit dem Spannungsteiler aus R3 und R6 realisiert wird.

Das Ausgangssignal von OP4, +12V oder -12V, ist nicht für einen Eingang mit TTL-Pegel gedacht, sondern für den Eingang Ring Indicator R1 der RS232-Schnittstelle, da sich hier gleich Interrupts höchster Priorität auslösen lassen und die RS232 meist noch nicht belegt ist. Auch die Versorgungsspannung der Schaltung läßt sich hier abzapfen; dazu wird die Ausgangsleitung RTS auf logisch 1 (+12V) gesetzt und DTR auf logisch 0 (-12 V). Die Treiberbausteine MC 1488 im Computer liefern 10mA, die Schaltung schluckt keine 3mA, man kann also die Datenleitungen als Lieferanten einer Versorgungsspannung mißbrauchen. Die zwei Dioden schützen die Schaltung vor einer verpolten Spannung, wenn die Ausgangsleitungen genau andersherum gesetzt sind. C2 schließlich, sehr wichtig, glättet die Spannung.

Aufbau und Betrieb der Schaltung

Der Aufbau der Schaltung erfolgt am elegantesten in der Posthaube der RS232-Buchse, wodurch man auf ein separates Gehäuse verzichten kann. Da es im wesentlichen zwei Bauformen dieser Posthauben gibt, sind in Bild 3 jeweils zwei Platinen-Layouts, Bestückungs- und Verdrahtungspläne abgebildet. Wer keine Platine ätzen will, kann auch eine kleine Lochrasterplatine verwenden und die Verbindungen in Fädeltechnik, mit Schaltdraht oder Litze herstellen. Pinzetten und etwas Ausdauer sollte man dabei jedoch besitzen. Die Platine wird in jedem Fall zwischen die zwei Anschlußreihen der DSub25-Buchse (Lötkelch-Version) gesteckt, wodurch sie mechanisch recht stabil fixiert wird. Auf einen IC-Sockel für den LM324 muß wegen der geringen Innenhöhe der Posthaube verzichtet werden.

Auf der Platinenoberseite stellt ein kurzer Draht die Verbindung von Pin 4 der Buchse zur Platine her, auf der Unterseite werden die Pins 20 und 22 entsprechend mit isoliertem Schaltdraht angeschlossen. Außerdem sind je nach Version ein oder zwei Drahtbrücken erforderlich. Der Fototransistor BPW40 schließlich sollte über ein ausreichend langes Kabel, am besten Koaxialkabel, angeschlossen werden. Er besitzt ein 5mm-LED-Gehäuse, die abgeflachte Seite mit dem kürzeren Draht ist der Kollektor (C). Man sollte dem Fototransistor unbedingt eine LED-Fassung mit Innenreflektor spendieren, die Richtcharakteristik wird dann besser. Wer einen anderen Fototransistor verwendet, und das sollte niemand tun, muß eventuell mit R5 die Spannungsverstärkung etwas ändern.

Die Schaltung ist so ausgelegt, daß ein Empfang von Daten bei einem Abstand von 20cm zwischen Taschenrechner und Empfänger immer möglich sein müßte. Wir haben zwar auch schon über eine Entfernung von 60cm Daten übertragen, aber dabei war der Empfang von den Lichtverhältnissen abhängig. Ohnehin kann es nur sinvoll sein, eine kleinere Entfernung zu wählen. Je kleiner der Abstand ist, desto geringer ist auch die Wahrscheinlichkeit, daß tieffliegende Wellensittiche den Strahlengang kreuzen und die Übertragung behindern.

Die Übertragungsgeschwindigkeit läßt sich vom Taschenrechner aus einstellen, beim HP-28S z.B. muß man 52 SF eingeben, um die höhere von zwei Geschwindigkeiten zu wählen.

Äußerst störend ist übrigens konzentriertes Glühlampenlicht, denn Glühlampen werden mit Wechselspannung betrieben und senden daher - auch im Infrarotbereich - Lichtpulse mit einer Frequenz von 100Hz aus. Wenn also eine Schreibtischlampe direkt auf den Empfänger scheint, kann es Übertragungsfehler geben, das normale Deckenlicht hingegen stört in der Regel nicht. Im übrigen ließe sich mit dem Fototransistor vor der Glühlampe sogar die Netzfrequenz messen oder auch die Bildwiederholungsfrequenz des Monitors.

Vom Lichtpuls zum Byte

Damit die Schaltung arbeiten kann, muß zunächst die Versorgungsspannung eingeschaltet werden. Dies geschieht in der abgedruckten Software automatisch, es werden im Soundchip Port A Bit 3 gesetzt und Bit 4 gelöscht. Bit 6 des MFP-I/O-Ports SFFFA01 kann nun gelesen werden. Wegen des Atari-internen Inverters bedeutet hier 0 = "Licht" und 1 = "kein Licht".

An diesem Punkt konnten wir zunächst eine Test-Software schreiben, die ein Digitalspeicheroszilloskop simulierte und die Lichtpulse grafisch (ein seltener Fisch!) auf dem Bildschirm darstellte (Bild 4). Es zeigte sich, daß alle Lichtpulse die gleiche Länge haben und nur ihr zeitlicher Abstand relevant ist. Jedes Byte, das der Taschenrechner sendet, beginnt mit einer Kennung aus drei Startpulsen, deren Abstand je eine Einheit (1T = ca. 430ps) beträgt. Danach werden 12 Bits seriell übertragen. Jedes Bit benötigt eine Zeit von 2T. Kommt in der ersten Hälfte der 2T ein Lichtpuls (also Puls und Lücke), ist es ein 1-Bit, kommt der Lichtpuls in der zweiten Hälfte (also Lücke und Puls), ist es ein 0-Bit. Von den 12 Bits dienen die ersten vier nur der Fehlererkennung und -korrektur, danach folgen die acht Daten-Bits.

Bild 3: Platinen-Layout, Bestückungs- und Verdrahtungsplan des Empfängers für zwei Gehäuseversionen. Die Platinen-Layouts sind seitenverkehrt, die Schrift „LS“ (Lötseite) muß auf der Platine lesbar sein.

Die Fehlerkorrektur (EDC = Error Detection and Correction) geschieht nach dem Hamming-Code (siehe Tietze, Schenk: Halbleiter-Schaltungstechnik, Springer-Verlag). Dabei werden vier gerade Paritäten über je vier oder fünf Bits gebildet. Ein Paritäts-Bit hat dabei genau dann den Wert 0, wenn die Anzahl der Einsen unter den betrachteten Bits gerade ist, was über eine „exklusiv-oder“-Verknüpfung erreicht wird. Entsprechend der Tabelle, obere Zeile, wird z.B. das Paritäts-Bit 3 gebildet, indem die Daten-Bits d6, d5, d4 und d3 EOR-verknüpft werden. Vom Taschenrechner werden die EDC-Bits aus den Daten-Bits berechnet und mit übertragen. Der Empfänger berechnet sie ebenfalls und vergleicht sie mit den empfangenen EDC-Bits. Aus den Abweichungen zwischen berechneten und empfangenen Bits kann anhand der Tabelle eindeutig bestimmt werden, welches Daten-Bit fehlerhaft ist, sofern nur ein Bit fehlerhaft übertragen wurde. In diesem Fall kann das Bit durch Negation korrigiert werden. Weichen z.B. EDC-Bit 3 und 0 ab, muß d3 das fehlerhafte Bit gewesen sein. Weicht nur ein EDC-Bit ab, ist es vermutlich selbst fehlerhaft übertragen worden, und der Fehler kann ignoriert werden.

Daten-Bit d7 d6 d5 d4 d3 d2 d1 d0
EDC-Bit 3 X X X X
EDC-Bit 2 X X X X X
EDC-Bit 1 X X X X X
EDC-Bit 0 X X X X

Tabelle: Verwendete Hamming-Matrix zur Fehlerkorrektur

Die Auswertung der ankommenden Lichtpulse geschieht in der abgedruckten Software elegant im Hintergrund. Bei der steigenden Flanke jedes Pulses wird vom MFP ein Interrupt ausgelöst und das Maschinenspracheprogramm angesprungen. Dort wird ein Timer gestartet, und so kann die Zeit zwischen zwei Pulsen ermittelt werden, ohne daß das Auswertungsprogramm die gesamte Systemzeit belegt. Auf detektierte Pulse muß dabei sofort mit einem Interrupt reagiert werden, um die Abstände korrekt erfassen zu können. Deshalb mußte der System-Timer gesperrt werden, da der Prozessor be i diesem Timer-Interrupt alle MFP-Interrupts sperrt. Aus dem Abstand zweier Pulse kann in Abhängigkeit vom letzten empfangenen Bit der Wert des folgenden Bits ermittelt werden. Folgt z.B. auf ein 1-Bit ein Puls mit einem Abstand von 2T, so steht er für ein weiteres 1-Bit. Zwischen dem Puls eines 0-Bits und dem eines 1-Bits hingegen ist ein Abstand von nur IT.

Wurden auf diese Weise alle zwölf EDC-und Daten-Bits empfangen und in einem Bit-Puffer abgelegt, berechnet das Programm daraus das Daten-Byte und korrigiert eventuell aufgetretene EDC-Fehler (haben wir erst einmal erlebt). Das Byte wird dann in einem Datenpuffer abgelegt, und zwar als Wort. Das Lo-Byte ist dabei das Daten-Byte, das Hi-Byte kann dabei eine Fehlernummer sein (siehe unten). Das Maschinenprogramm tut also nichts weiter, als Daten zu empfangen und im Puffer abzulegen, wovon das Verwaltungsprogramm zunächst nichts bemerkt.

Bild 4: Die Codierung der Daten bei der seriellen Übertragung

Die Handhabung des Maschinenprogramms

Das Maschinenspracheprogramm kann leicht in C eingebunden werden. Lediglich vier Funktionen dienen zur Installation und Datenübergabe:

**void buf_init(int start, int end);
legt fest, welcher Speicherbereich als Datenpuffer verwendet werden soll, start zeigt dabei auf das erste Wort des Pufferbereichs, und end zeigt hinter das letzte Wort des Puffers. Der Speicherbereich ist vorher vom C-Programm aus zu reservieren.

void install(void);
schaltet die Versorgungsspannung der Hardware ein und installiert die Interrupt-Routinen. Der Empfangsmodus wird so einmalig vor der Übertragung aktiviert. Der Aufruf muß im Supervisormodus erfolgen, also als Supexec(install);.

int buf_get(void);
holt ein Datenwort aus dem Datenpuffer. Das Lo-Byte enthält das empfangene Byte, das Hi-Byte ist eventuell eine Fehlermeldung. Folgende Kombinationen sind möglich:

$00xx Daten-Byte xx OK 
$0100 Puffer leer, keine Daten 
$02xx korrigierter EDC-Fehler 
$03xx fataler EDC-Fehler 
$0400 Pufferüberlauf, Datenverlust 
$0500 illegaler Pulsabstand

Dabei deutet Fehlemummer $0500 darauf hin, daß die optischen Empfangsbedingungen zu schlecht sind. Es sollte eine kleinere Übertragungsentfemung gewählt werden. Fehler $0400 tritt auf, wenn bei einem zu kleinen Pufferbereich Daten empfangen werden, diese aber nicht ausgelesen werden.

void i_remove(void);
bewirkt das genaue Gegenteil von install und beendet die Empfangsbereitschaft. Der Aufruf erfolgt ebenfalls im Supervisormodus.

Die abgedruckte Minimal-Software

Während das Maschinenspracheprogramm vollständig ist, handelt es sich beim abgedruckten C-Programm um eine Minimal-Software. Mit ihr können Texte und Grafiken nur empfangen und auf Bildschirm oder Drucker dargestellt werden. Das Format, in dem der HP Grafiken sendet, ähnelt übrigens dem bei Matrix-druckem üblichen Format. Nachdem ESC-Code #27 folgt ein Byte, das die Anzahl der Grafikspalten angibt, und dann werden die Daten der Grafikspalten gesendet, je acht Pixel übereinander, wobei allerdings das höchste Bit das unterste Pixel repräsentiert.

Bei den abgedruckten Listings handelt es sich um den C-Quelltext für Turbo C, den Maschinensprachequelltext für den MAS 68K-Assembler und um die Projektdatei, mit deren Hilfe Turbo C in Verbindung mit dem MAS 68K die Programmteile automatisch compilieren, assemblieren und linken kann. Ab Turbo C-Version 2.0 wurde leider in der include-Datei tos.h die Deklaration der Funktion Supexec geändert. Um die daraus resultierenden Fehler zu umgehen, ist daher für Versionen vor 2.0 im C-Quelltext die markierte Zeile 27 zu löschen.

Wird das Programm gestartet, meldet es EMPFANGSBEREIT. Werden jetzt Texte und Grafiken vom Taschenrechner gesendet, erscheinen sie zunächst nur auf dem Bildschirm. Erst, wenn das Programm über die ESC-Taste beendet wird, wird eine Datei hp_print.prn erzeugt, die alle empfangenen Texte und Grafiken enthält und vom Desktop aus direkt an den Drucker geschickt werden kann. Ein simultanes Schreiben oder gar Drucken dieser Daten war nicht möglich, da diese Operationen ebenso zeitkritisch sind wie die Empfangsroutinen. Eine gegenseitige Beeinflussung hätte zu Störungen geführt.

Die Minimal-Software ist aus Platzgründen nicht ganz sauber programmiert, daher läuft sie nur in den drei Standardauflösungen. Eine wesentlich komfortablere, sauber programmierte und GEM-unterstützte Version der Software ist in Arbeit und wird voraussichtlich über den PD-Service zu beziehen sein, da sie zum Abtippen zu lang ist. Mit dieser Version ist es auch möglich, die Programm-Listings vom HP umzuformatieren. Die kassenzettelbreiten Listings mit nur 24 Zeichen pro Zeile können auf eine übersichtlichere Form gebracht. Grafiken auch vergrößert ausgedruckt werden, und der gesamte Zeichensatz des HP wird vom Drucker beherrscht.

Bidirektionale Datenübertragung

Das jüngste Kind von Hewlett-Packard, der HP-48SX, besitzt bereits eine bidirektionale optische Schnittstelle, mit der auch Daten und Programme von Rechner zu Rechner übertragen werden können. Es wäre daher denkbar, auch dem Atari noch einen optischen Sender zu verpassen. Als Standard möchten wir hiermit festlegen, daß dazu eine IR-Sendediode über einen Widerstand zwischen Pin 20 (DTR) und Pin 7 (Masse) der RS232 geschaltet wird. Zum Senden kann dann RTS auf 0 gesetzt werden, die Versorgungsspannung des Empfängers wird so ausgeschaltet, und über DTR ist dann die Sendediode an- und ausschaltbar. Programme vom Taschenrechner ließen sich so zum Atari übertragen, abspeichern, kopieren und an einen anderen Taschenrechner senden, und man könnte so einen HP-Software-Pool organisieren. Bisher ist das Zukunftsmusik, doch wenn uns jemand einen HP-48SX schenkt, schreiben wir die nötige Software. versprochen.

Dirk Schwarzhans, Lukas Bauer

Bauteileliste

   
OP1...4 1 Stk. LM324
D1,D2 2 Stk. 1N4148
T1 1 Stk. BPW40
C1 1 Stk. 100nF/25V
C2 1 Stk. 10uF/25V
R1...3 3 Stk. 1kΩ
R4...6 3 Stk. 33kΩ
1 Stk. Innenreflektor für 5mm LED
1 Stk. DSub25-Buchse. weiblich, Lötkelch-Version
1 Stk. Posthaube für DSub25 Kabel, (Lochraster-)Platine

Die Bauteile kosten komplett 10,- DM.

;   Hewlett-Packard an ST: "Bitte kommen!"
;   Minimal-Software zum Datenempfang vom HP
;       Projektdatei "HP_TO_ST.PRJ"
;   by Lukas Bauer und Dirk Schwarzhans
;   (C) 1991 MAXON Computer
HP_TO_ST.PRG        ; Name des ausführbaren Progr.
.C [-W-pia]         ; Warnung "poss. inc. ass." aus

=                   ; Trennzeichen

TCSTART.O           ; Startcode

HP_TO_ST.C          ; Name des C-Quelltextes
HP_INTER.S [-S]     ; Name des MAS 68K-Quelltextes

TCSTDLIB.LIB        ; Standard-Bibliothek
TCEXTLIB.LIB        ; Erweiterte Bibliothek
TCTOSLIB.LIB        ; TOS-Bibliothek
TCLNALIB.LIB        ; LINEA-Bibliothek
; Hewlett-Packard an ST: "Bitte kommen!"
; Minimalsoftware zum Datenempfang vom HP 
; MAS 68K-Quelltext "HP_INTER.S"
; by Lukas Bauer und Dirk Schwarzhans 
; (C) 1991 MAXON Computer

    export buf_init     ; Puffer Start- und Endadresse festlegen 
    export install      ; Spannung an, Interrupts installieren 
    export buf_get      ; Datenwort aus Puffer holen 
    export i_remove     ; Spannung aus, Interrupts entfernen

dummy           equ $DEADFACE
iv_acia         equ $00000118   ; Interrupt-Vektor f.MIDI u.Tastatur 
iv_ring         equ $00000138   ; Interrupt-Vektor f.Ring Indicator 
iv_timer        equ $00000134   ; Interrupt-Vektor für Timer A
aer             equ $FFFFFA03   ; Aktive Edge Register
iera            equ $FFFFFA07   ; Interrupt Enable Register A
ierb            equ $FFFFFA09   ; Interrupt Enable Register B
imra            equ $FFFFFA13   ; Interrupt Mask Register A
imrb            equ $FFFFFA15   ; Interrupt Mask Register B
isra            equ $FFFFFA0F   ; Interrupt In Service Reg. A 
gpip            equ $FFFFFA01   ; Datenport des MFP
tacr            equ $FFFFFA19   ; Timer A Control Register
tadr            equ $FFFFFA1F   ; Timer A Data Register
psgregsel       equ $FFFF8800   ; Soundchip Register Select 
psgrd           equ $FFFF8800   ; Soundchip Register Read 
psgwr           equ $FFFF8802   ; Soundchip Register Write

; Installiert den Timer-A- und den Ring-Indicator- (RI)-Interrupt 
install:    MOVE    SR,D0       ; Status merken
            ORI     #$0700,SR   ; alle Interrupts sperren
            BSR     power_on    ; Versorgungsspannung an 
            MOVE.L  #ring_irq,iv_ring.w ; RI-Int-Routine installieren 
            BCLR    #6,aer.w    ; Interrupt bei steig. Flanke 
            MOVE.L  #timer_irq,iv_timer.w ; Timer-IRoutine installieren 
            MOVE.B  #0,tacr.w   ; Timer A stoppen 
            MOVE.W  #$FF00,timer_hi ; High-Byte des Timer löschen 
            MOVE.B  #$FF,tadr.w ; Timer A mit Startwert laden 
            MOVE.B  #%00000011,tacr.w   ; Timer Start, Vorteiler 1:16 
            ORI.B   #%01100000,iera.w   ; RI-und Timer-A-
            ORI.B   #%01100000,imra.w   ; Interrupts freigeben 
            MOVE.L  $00000118.w,nijmp+2 ; Tastatur-Int.-Vektor merken 
            MOVE.L  #newirq,$00000118.w ; neuen installieren
            BCLR    #5,imrb.w   ; 200Hz-Systemtimer sperren 
            BCLR    #5,ierb.w
            CLR.W   pulsnum
            CLR.W   timeplus
            MOVE    D0,SR
            RTS 


; Interrupts wieder löschen
i_remove:   MOVE    SR,D0       ; Status merken
            ORI     #$0700,SR   ; alle Interrupts sperren 
            ANDI.B  #%10011111,imra.w   ; RI-und Timer-A-
            ANDI.B  #%10011111,iera.w   ; Interrupt sperren
            BSET    #5,imrb.w   ; 200Hz-Systemtimer freigeben
            BSET    #5,ierb.w
            CLR.L   iv_ring.w   ; RI-Interrupt-Vektor löschen 
            CLR.L   iv_timer.w  ; Timer-A-Int.-Vektor löschen 
            MOVE.L  nijmp+2,$00000118.w 
            BSR     power_off   ; Versorgungsspannung aus 
            MOVE    D0,SR       ; Int-Status wiederherstellen
            RTS

; Versorgungsspannung anschalten 
power_on:   MOVE.B  #14,psgregsel.w     ; Port A selektieren 
            MOVE.B  psgrd.w,D1  ; Zustand lesen
            AND.B   #%11100111,D1       ; RTS und DTR löschen
            OR.B    #16,D1      ; DTR auf Hi setzen
            MOVE.B  D1,psgwr.w 
            RTS

; Versorgungsspannung ausschalten 
power_off:  MOVE.B  #14,psgregsel.w     ; Port A selektieren
            MOVE.B  psgrd.w,D1          ; Zustand lesen
            AND.B   #%11100111,D1       ; RTS und DTR löschen
            MOVE.B  D1,psgwr.w 
            RTS

; Datenpuffer initialisieren
buf_init:   MOVE.L  A0,buf_start        ; Übergabe A0=Pufferstart
            MOVE.L  A0,next_in
            MOVE.L  A1,buf_end          ; Übergabe A1=Pufferende
            MOVE.L  A1,next_out 
            RTS

; Datenwort aus D0 in den Puffer schreiben 
buf_put:    MOVEA.L next_in,A0
            CMPA.L  next_out,A0         ; Puffer voll?
            BEQ     buf_full            ; ja, Fehler
            MOVE.W  D0,(A0)+            ; nein, dann Wort ablegen 
            CMPA.L  buf_end,A0          ; Puffer-Obergrenze erreicht?
            BNE     lab1
            MOVEA.L buf_start,A0        ; ja, dann Zeiger auf Anfang
lab1:       MOVE.L  A0,next_in          ; und Zeiger zurückschreiben
            RTS
buf_full:   CMPA.L  buf_start,A0        ; Zeiger auf Pufferanfang?
            BNE     lab2
            MOVEA.L buf_end+2,A0        ; letztes Wort am Pufferende 
lab2:       MOVE.W  #$0400,-2(A0)       ; letztes Wort im Puffer mit 
            RTS                         ; Fehlernummer überschreiben

; Datenwort aus Puffer lesen 
buf_get:    MOVEA.L next_out,A0
            ADDQ.L  #2,A0               ; Ausgabezeiger erhöhen
            CMPA.L  buf_end,A0          ; Pufferende erreicht?
            BLT     lab3
            MOVEA.L buf_start,A0        ; ja, wrap around
lab3:       CMPA.L  next_in,A0          ; Puffer leer?
            BEQ     buf_empty           ; ja
            MOVE.W  (A0),D0             ; nein, Wort aus Puffer holen 
            MOVE.L  A0,next_out         ; Ausgabezeiger rückschreiben
            RTS
buf_empty:  MOVE.W  #$0100,D0           ; Puffer leer, #$0100 zurück
            RTS

; wird bei einem Low-High-Wechsel an der RI-Leitung aufgerufen 
ring_irq:   CLR.B   tacr.w              ; Timer A stoppen
            BCLR    #5,iera.w           ; Timer A-Int. ignorieren 
            MOVEM.L D0-D3/A0-A1,-(SP)
            MOVE.W  timer_hi,D0         ; High-Byte des Timers 
            MOVE.B  tadr.w,D0           ; Low-Byte eintragen
            MOVEQ   #-1,D1
            MOVE.B  D1,timer_hi         ; Timer High-Byte löschen 
            MOVE.B  D1,tadr.w           ; Timer Low-Byte löschen 
            MOVE.B  #%00000011,tacr.w   ; Timer wieder starten 
            SUB.W   D0,D1               ; D1= Zeit seit letztem Int. 
            ADDQ.W  #4,D1               ; + Zeit bis Timer-Neustart 
            ADD.W   timeplus,D1         ; um Kurzpulslänge verlängern
            CLR.W   timeplus
            CMPI.W  #40,D1              ; Mindestpulslänge
            BHI     lab4                ; Pulslänge ausreichend? 
            MOVE.W  D1,timeplus         ; Nein, Kurzpuls-Länge merken 
            BRA     endri               ; und Puls ignorieren
lab4:       MOVE.W  pulsnum,D2
            ADDQ.W  #1,pulsnum          ; Pulsnummer erhöhen
            TST.W   D2                  ; erster Startpuls, nichts tun
            BNE     lab5
            CMPI.W  #$0100,D1           ; Zwischenbyte-Pause zu kurz? 
            BHI     endri               ; nein, Puls 1 war OK, Ende 
            CLR.W   pulsnum             ; ja, weiter auf Puls 1 warten 
            BRA     endri               ; und Ende
lab5:       CMP.W   #2,D2               ; 2. oder 3. Startpuls?
            BLE     startbits           ; ja, timebase ermitteln

datenbits:  LEA     bitbuf,A0           ; Speicher empfangene Bits 
            LEA     bittab0,A1          ; Tabelle für lastbit=0 
            TST.B   lastbit             ; war lastbit wirklich 0? 
            BEQ     lab6                ; ja, Tabellenzeiger OK 
            LEA     bittab1,A1          ; nein, Tabelle für lastbit=l1
lab6:       MOVE.W  timebase,D0         ; T in 6.51us
            MOVE.W  D0,D3
            LSR.W   #1,D0
            ADD.W   D0,D1               ; D1 alt: Pulsabstd in 6.5us 
            EXT.L   D1                  ; D1 neu: Pulsabstand in T
            DIVU    D3,D1               ; "D1 = INT(t/timebase+.5)"
            CMPI.W  #5,D1               ; Pulspause größer 5*timebase 
            BGT     err5                ; dann übler Fehler
            MOVE.B  0(A1,D1.w),D0       ; Bit aus Tabelle holen 
            MOVE.B  D0,lastbit          ; Bit merken 
            MOVE.B  D0,-3(A0,D2.w)      ; Bit speichern in Bitpuffer
            BMI     err5                ; $FF in Tabelle, dann Fehler 
            CMP.W   #14,D2              ; letztes Bit?
            BEQ     endbit              ; ja, Bits in Byte umwandeln

endri:      MOVEM.L (SP)+,D0-D3/A0-A1   ; Register wiederherstellen 
            BSET    #5,iera.w           ; Timer A Interrupt freigeben 
            BCLR    #6,isra.w           ; Interrupt beendet
            RTE                         ; Ende und aus.

startbits:  CMP.W   #52,D1              ; Zeit < 430us - 25% 
            BLT     err5                ; dann Zeitunterschreitung 
            CMP.W   #82,D1              ; Zeit > 430ms + 25% 
            BGT     err5                ; dann Zeitüberschreitung 
            CMP.W   #2,D2               ; 3. Startpuls?
            BNE     lab7                ; nein
            ADD.W   timebase,D1         ; ja, Zeiten addieren
            LSR.W   #1,D1               ; und Mittelwert bilden
            CLR.B   lastbit             ; erstes Bit wie nach 0-Bit
lab7:       MOVE.W  D1,timebase         ; Länge von T in 6.51us
            BRA     endri

; Fehler $0500: Illegaler Pulsabstand bei Startpulsen oder Datenbits 
err5:       MOVE.W  #$0500,D0           ; Fehlernummer nach D0
            MOVE.W  #1,pulsnum          ; nächster Puls ist 2
initnext:   BSR     buf_put             ; D0 in den Puffer schreiben
            BRA     endri

; berechnet aus empf. Bitmuster das Byte und korrigiert nach Hamming-EDC 
endbit:     MOVEQ   #11,D0              ; 12 EDC- und Datenbits
loop_roxr:  MOVE.B  0(A0,D0.w),D2       ; Bit aus Bitpuffer 
            ROXR.B  #1,D2               ; ins X-Flag rollen
            ROXR.W  #1,D1               ; und in D1 einrollen
            DBRA    D0,loop_roxr        ; Schleife über 12 Bit
            LSR.W   #4,D1               ; D1= 0000eeeedddddddd 
            LEA     hamming,A1          ; EOR-Maskentabelle EDC
            MOVEQ   #0,D2               ; Vorbelegung für EDC
            MOVEQ   #7,D3               ; EDC über 8 Datenbits
loop_edc:   MOVE.B  0(A1,D3.w),D0       ; Maske für EDC-EOR
            BTST    D3,D1               ; Datenbit prüfen
            BEQ     skip_eor            ; Bit 0, kein EOR
            EOR.B   D0,D2               ; Bit 1, dann EOR durchführen
skip_eor:   DBRA    D3,loop_edc         ; Schleife über 8 Datenbits 
            MOVE.W  D1,D3               ; empfang. EDC-Bits abtrennen
            LSR.W   #8,D3               ; D3= 000000000000eeee
            ANDI.W  #$00FF,D1           ; D1= 00000000dddddddd
            EOR.B   D3,D2               ; Vergleich mit berechn. EDC 
            ASL.W   #1,D2               ; Zeiger = Syndromwort mal 2
            LEA     edctab,A0           ; A0 auf Korrekturtabelle
            MOVE.W  0(A0,D2.w),D0       ; Korrekturwort aus Tabelle 
            EOR.W   D1,D0               ; EOR korrigiert das Bit
            CLR.W   pulsnum             ; Nächster Puls 1. Startpuls
            BRA     initnext            ; Wort ablegen und Ende

; erzeugt Hi-Byte für Timer-A, indem Nulldurchgänge gezählt werden 
timer_irq:  BCLR    #6,imra.w           ; sperrt RI-Interrupt 
            TST.B   timer_hi            ; schon auf 0 gezählt, dann 
            BEQ     skip_count          ; nicht mehr weiterzählen 
            SUBQ.B  #1,timer_hi         ; Timer Hi-Byte weiterzählen 
skip_count: BCLR    #5,isra.w           ; Ende Interruptbehandlung 
            BSET    #6,imra.w           ; RI-Interrupt freigeben
            RTE

; neuer Tastatur-Interrupt, Adresse dummy wird überschrieben 
newirq:     ORI     #$0700,SR
            ANDI    #$F5FF,SR           ; IPL=5 setzen
nijmp:      JMP     dummy               ; alten Interrupt ausführen

            data
bittab1:    dc.b    $FF,$FF,1,0,1,0     ; Tabelle bei lastbit=1
bittab0:    dc.b    $FF,1,0,1,0,$FF     ; Tabelle bei lastbit=0

hamming:    dc.b    %00000011,%00000101,%00000110,%00001001 
            dc.b    %00001010,%00001100,%00001110,%00000111

; Tabelle für die Fehlerkorrektur, Zeiger ist Syndromwort 
edctab:     dc.w    $0000,$0200,$0200,$0201    ; Lo-Byte: Korrekturmaske, zu 
            dc.w    $0200,$0202,$0204,$0280    ; invertierendes Bit ist 1 
            dc.w    $0200,$0208,$0210,$0300    ; Hi-Byte: Fehlernummer, wird
            dc.w    $0220,$0300,$0240,$0300    ; d. EOR ins Wort gemischt

            bss
buf_start:  ds.l 1  ; phys. Pufferstartadresse
buf_end:    ds.l 1  ; phys. Pufferendadresse +2
next_in:    ds.l 1  ; nächste freie Stelle im Puffer
next_out:   ds.l 1  ; Adresse des letzten ausgelesenen Wortes 
timer_hi:   ds.w 1  ; Hi-Byte Zeitzähler zwischen zwei Pulsen 
timebase:   ds.w 1  ; Länge einer halben Bitbreite T in 6.51us 
timeplus:   ds.w 1  ; Zeit-Offset nach zu kurzem Pulsabstand 
pulsnum:    ds.w 1  ; Nummer des erwarteten Pulses minus 1, 0-14 
lastbit:    ds.b 1  ; Wert des letzten Bit
bitbuf:     ds.b 12 ; Puffer für die 12 seriell kommenden Bits
            end
/*                                              */
/*  Hewlett-Packard an ST: "Bitte kommen!"      */
/*  Minimal-Software zum Datenempfang vom HP    */ 
/*         C-Quelltext "HP_TO_ST.C"             */
/*  by Lukas Bauer und Dirk Schwarzhans         */
/*      Ausgabe auf dem Bildschirm und          */
/*   in die Protokolldatei "HP_PRINT.PRN"       */
/*  (C) 1991 MAXON Computer                     */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <tos.h>
#include <linea.h>
#include <ext.h>

#define BUFLEN 1000 /* Länge Empfangspuffer  */
#define SCALE 2     /* Grafik Vergrößerung   */
#define LIST 0      /* Flag Textbildschirm   */
#define GRAF 1      /* Flag Grafikbildschirm */
#define FILENAME "HP_PRINT.PRN" /* Protokoll */ 
#define PRNSIZE 200000L /* max. Dateigröße   */
                    /* der Protokolldatei    */

/* Maschinensprache-Routinen */ 
extern void buf_init(int *, int *);     /* Pufferbereich festlegen */ 
extern int buf_get(void);            /* Datenwort aus Puffer holen */

/* nächste Zeile löschen bei                                       */
/* Turbo C Version kleiner 2.0 !!!!!!!!!!!                         */
#define TURBO_C_2_0 true

#ifdef TURBO_C_2_0
    extern long install(void); 
            /* Spannung an, Interrupt installieren */ 
    extern long i_remove(void);
            /* Spannung aus, Interrupt entfernen   */
#else
    extern void install(void); 
    extern void i_remove(void);
#endif

/* Funktionsprototypen */ 
int     meminit(void); 
void    graf_out(int); 
int     char_wait(void); 
void    screen(int); 
void    plot(int, int);
void    end_prog(void); 
void    prn(char *); 
void    prnc(char);

int     *memptr;    /* Speicher für Empfangspuffer */
char    *ts, *gs,
        /* Zeiger auf Text- und Grafikbildschirm */ 
        *gmem; /* Speicher für Grafikbildschirm  */ 
int     pattern = 0xFFFF; /* Linientyp für LINEA */ 
char    *prnbufs,   /* Zeiger auf Start und      */
        *prnbufe,   /* Ende des Protokollpuffers */ 
        *prnbuf;/* Eingabezeiger Protokollpuffer */

/* ---------------------- */
/* Hauptprogramm          */
/* ---------------------- */

int main(void)
{
    int data,           /* Empfangenes Datenwort  */
        gflag;          /* Flag für Grafikausgabe */

    if (meminit())      /* Speicher reservieren   */
    {
        puts("Nicht genügend Speicher frei !"); 
        return -1;
    }

    puts("\033p EMPFANGSBEREIT \33q"); 

    do
    {
        data = char_wait(); /* auf Zeich. warten */ 
        switch (data)
        {
            case 27:            /* Grafikdaten ? */
               data = char_wait();
               if (data > 0 && data <= 166) /* Anz. */
               {
                  graf_out(data); /* Grafik ausgeben */ 
                  gflag = 1;
               }
               break;
            case 4: /* cariage return & linefeed */
               if (!gflag)
               {
                  puts (""); 
                  prn("\r\n");
               }
               break;
            default:                 /* Textausgabe */
               screen(LIST); 
               gflag = 0;
               switch (data) /* einige Sonderzeichen */ 
               {             /* umwandeln            */
                  case 146:
                     putch(174); /* Doppelklammer << */
                     prnc(174); 
                     break; 
                  case 147:
                     putch(175); /* Doppelklammer >> */
                     prnc(175); 
                     break; 
                  case 141:
                     putch('-');         /* Pfeil -> */
                     putch('>'); 
                     prn("-\010>"); 
                     break;
                  default:       /* sonstige Zeichen */
                     putch(data); /* nicht umwandeln */ 
                     prnc(data);
               }
         }
    }
    while (1);
    }

/*                                                 */
/* Reserviert Speicher, initialisiert die          */
/* Interrupts und LINEA-Routinen                   */
/*                                                 */
/* Rückgabe int: Null bedeutet kein Fehler         */
/*                                                 */

int meminit (void)
{
   /* LINEA Einstellungen */ 
   linea_init(); 
   set_fg_bp(1); 
   set_ln_mask(0xFFFF); 
   set_wrt_mode(0); 
   set_pattern(&pattern, 0, 0); 
   set_clip(0, 0, 0, 0, 0); 
   hide_mouse();

   ts = Logbase();     /* Bildschirmadresse holen */

   if ((memptr = Malloc(BUFLEN*sizeof(int))) < 0) 
      return -1; 
   if ((gmem = Malloc(32256)) < 0)
   {
      Mfree(memptr); 
      return -1;
   }
   if ((prnbuf = prnbufs = Malloc(PRNSIZE)) < 0)
   {
      Mfree(memptr);
      Mfree(gmem); 
      return -1;
   }
   prnbufs = prnbufs + PRNSIZE;

   /* Bildschirmadr. auf 256Byte-Grenze runden */ 
   gs = (char *)((long)(gmem+256) & 0xFFFFFF00L);

   /* Puffer setzen, Interrupt installieren,    */
   /* Abbruch-Routine festlegen                 */
   buf_init(memptr, memptr + BUFLEN);
   Supexec(install); 
   atexit(end_prog);

   /* Text- und Grafikbildschirm löschen        */ 
   Setscreen((char *)-1L, gs, -1); 
   puts("\033E");
   Setscreen((char *)-1L, ts, -1); 
   puts("\033E\033v");

   return 0;
}

/*                                                 */
/* Setzt einen Grafikpunkt der Größe "SCALE"       */
/*                                                 */
/* int x,y: Koordinaten des Punktes                */
/*                                                 */

void plot(int x,int y)
{
   filled_rect(x * SCALE, y * SCALE,(x + 1) * SCALE - 1,(y + 1) * SCALE - 1);
}

/*                                                 */
/* Wartet auf ein Zeichen vom HP.                  */
/* Fehler werden ignoriert.                        */
/* ESC-Taste des ST beendet das Programm.          */
/*                                                 */
/* Rückgabe int: vom HP gesendetes Zeichen         */
/*                                                 */

int char_wait(void)
{
   int temp;                   /* empfangenes Wort */

   do
   {
      temp = buf_get();      /* auf Zeichen warten */ 
      if ((char)Crawio(0xFF) == 27)
         exit(0);          /* ESC-Taste, dann Ende */
   }
   while (temp & 0xFF00);      /* Fehler ignoriren */

   return temp;
}

/*                                                 */
/* schaltet Text- oder Grafik-Bildschirm ein       */
/*                                                 */
/* int which: LIST = Textbildschirm                */
/* oder GRAF = Grafikbildschirm                    */
/*                                                 */

void screen(int which)
{
   if (which == LIST)
      Setscreen(ts, ts, -1);     /* Textbildschirm */ 
   else
      Setscreen(gs, gs, -1);     /* Grafikbildsch. */
}

/*                                                 */
/* Beim Programmende mit exit() wird diese         */
/* Routine aufgerufen, die die Interrupts          */
/* löscht und den Speicher freigibt                */
/*                                                 */

void end_prog (void)
{
   int handle;

   Supexec(i_remove);        /* Interrupts löschen */
   screen(LIST);      /* alten Bildsch. einstellen */

   /* Protokollpuffer Speichern */ 
   if ((handle = Fcreate(FILENAME, 0)) > 0)
   {
      Fwrite(handle, prnbuf - prnbufs, prnbufs); 
      Fclose(handle);
   }

   Mfree(gmem);             /* Speicher freigeben */
   Mfree(memptr);
   Mfree(prnbufs);
   show_mouse(1);               /* Maus wieder an */
}

/*                                                 */
/* Empfängt Grafikdaten und stellt sie dar         */
/*                                                 */
/* int anz: Anzahl der erwarteten Grafikdaten      */
/*                                                 */

void graf_out(int anz)
{
   static int  y = 0; /* y-Koord. Grafikcursor     */ 
   int         x,     /* x-Koord. Grafikcursor     */
               b,     /* Bitzähler                 */
               db,    /* Druckerbyte               */
               i,     /* Schleifenvariable         */
               data;  /* Datenwort vom HP          */

   screen(GRAF);      /* Grafikbildschirm an       */
   prn("\033K");      /* Drucker-Grafik 60 dpi     */
   prnc(anz); 
   prnc(0);

   if (y * SCALE >= 384)
   {
      set_fg_bp(0); /* Bildschirm löschen */
      filled_rect(0 ,0 ,639 ,399 ); 
      set_fg_bp(1); 
      y = 0;
   }

   for (x = 1; x <= anz; x++) /* Empfangsschl.    */
   {
      data = char_wait(); /* Grafikbyte warten    */ 
      db = 0;
      for (b = 1, i = 0; i < 8; b <<= 1, i++)
         if (data & b)      /* Grafikbit gesetzt? */
         {
            db |= (1 << (7-i));
            plot(x, y + i);       /* Punkt setzen */
         }
      prnc(db);
   }
   y += 8;                      /* Zeilenvorschub */
   prn("\015\033J\030");           /* 24/180 Zoll */
}

/*                                                 */
/* Schreibt String in den Protokollpuffer          */
/*                                                 */
/* char *string: Zeiger auf den String             */
/*                                                 */

void prn(char *string)
{
   if (prnbuf + strlen(string) < prnbufe) 
   strcpy(prnbuf, string); 
   prnbuf += strlen(string);
}

/*                                                 */
/* Schreibt ein Zeichen in den Protokollpuffer     */
/*                                                 */
/* char byte: Zu schreibendes Zeichen              */
/*                                                 */

void prnc(char byte)
{
   if (prnbuf < prnbufe - 1)
      *(prnbuf++) = byte;
}


Aus: ST-Computer 03 / 1991, Seite 55

Links

Copyright-Bestimmungen: siehe Über diese Seite