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