← ST-Computer 03 / 1991

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

Hardware

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; }