Kommunikation via GPIB mit dem AVR

This Article in english: 

Im folgenden sind meine Experimente zum Thema Nutzung des GPIB-Busses mit AVR Mikrocontrollern beschrieben.

Hinweis: Beschrieben ist -wie immer auf meinen Seiten- ein Versuchsaufbau im Experimentallabor. Es handelt sich keinesfalls um produktiv nutzbare Hard- oder Software. Der Versuchsaufbau kann allerhöchstens Basis für eigene Versuche dienen. Ich schreibe dies, weil GPIB-fähige Geräte typischerweise sehr teuer in der Anschaffung sind. Man kann sich beim Experimentieren durchaus den GPIB-Anschluss kaputt machen, daher diese ausdrückliche Warnung. Sagen Sie nicht ich hätte Sie nicht gewarnt 🙂

Der GPIB (General Purpose Interface Bus) ist ein von HP in den sechziger Jahren entwickeltes Bus-System. Es wurde vorrangig für die Vernetzung komplexer Messgeräte entwickelt, um z.B. automatisiertes Testen zu ermöglichen. Das Bus-System wurde durch HP und andere Hersteller weiter entwickelt und ausgebaut und unter dem Namen IEEE 488 standardisiert. Der Bus ist auch unter den Namen HPIB und IEC 625 bekannt.

Im Bereich hoch entwickelter Messgeräte ist der GPIB-Bus auch über 40 Jahre nach seiner Entwicklung noch weit verbreitet. Insbesondere Gebrauchtgeräte aus den 80ger und 90er Jahren haben oft ein GPIB-Interface.

Mein bei eBay erstandenes digitales Speicheroszilloskop Tektronix 2432A von 1989 besitzt ein GPIB-Interface. Das Oszilloskop lässt sich mittels GPIB komplett fernsteuern, es lassen sich alle Einstellungen abfragen und die momentan gespeicherten/dargestellten Wellenformen auslesen. Die Idee war daher, vom PC aus auf diese Daten zuzugreifen.

Tektronix 2432a Cursor

Es gibt für PCI, USB, ISA und RS232 Karten und Geräte, welche mit dem Rechner verbunden werden können und die eine GPIB-Schnittstellen besitzen. Diese Karten und Geräte sind sehr teuer, ich habe keine Lösung -auch nicht gebraucht- für unter 100 Euro gesehen. Ich habe daher versucht, mit einem AVR Mikrocontroller die Funktion eines GPIB-Geräts nachzubilden.

Der GPIB-Bus ist ein paralleler Bus. Geräte können mit speziellen Kabeln, die an den Enden sowohl eine Buchse als auch einen Stecker besitzen, in einer „Daisy Chain“ verbunden werden. Jedes Gerät am Bus hat typischerweise zwei Adressen (eine Listener und eine Talker-Adresse). An einem Bus können maximal 15 Geräte adressiert werden. Eines der Geräte fungiert als Controller in Charge. Der Controller steuert den Datenfluss zwischen allen anderen Geräten mittels Bus-Kommandos. Es kann auf einem Bus immer nur einen Talker gleichzeitig geben, es kann aber immer mehrere Listener geben. Typischerweise kann man bei einem GPIB-Gerät dessen Adresse und dessen Verhalten einstellen. Man kann z.B. ein Gerät als Listener, als Talker oder als Listener+Talker einstellen.

Trivialansatz: Talker/Listener Szenario

Falls man genau einen Talker und einen Listener hat, benötigt man keinen Controller. Der Talker gibt seine Daten auf den Bus aus, ohne einen speziellen Listener zu adressieren und der Listener liest die Daten vom Bus herunter. Dieser Fall tritt auf, wenn man an ein Oszilloskop einen Drucker oder Plotter anschließt. Diesen Fall habe ich zuerst aufgebaut.

Hardware

Ich habe einen ATMega32 genommen und diesen auf übliche Weise mittels RS232 mit meinem PC verbunden. Der AVR wird dann direkt mit dem GPIB-Bus verbunden. Hierbei sind einige Dinge zu beachten, die im weiteren erwähnt werden.

Die Leitungen des GPIB-Busses

Der GPIB-Bus wird auf eine 24-polige Centronics-Buchse geführt. 8 Leitungen (DIO1..8) transportieren das Datenbyte. Drei Leitungen (DAV, NRFD und NDAC) sind für den Handshake auf dem Bus verantwortlich. 5 Leitungen (ATN, SRQ, EOI, REN, IFC) sind für das Management des Busses verantwortlich. Somit benötigt man für eine Vollbelegung 16 Leitungen. Die restlichen Pins der Buchse sind Masse-Leitungen.

Tektronix 2432a GPIB Buchse

Die Leitungen des Busses sind „Open Collector“ beschaltet, und „Active Low“. D.h. eine logische „0“ bedeutet, dass die Leitung HIGH, also „1“ ist. Eine logische „1“ bedeutet, dass die Leitung LOW, also „0“ ist. Die Open Collector-Beschaltung sorgt dafür dass mehrere Listener gleichzeitig an einer Leitung hängen können und unabhängig voneinander die Leitung auf LOW schalten können, ohne das dabei eine Ausgangsstufe eines der Geräte kaputt geht.
Solange nur eine Konfiguration Talker->Listener vorhanden ist und kein weiterer Listener im Spiel, braucht man auf das Thema Open Collector nicht zu achten.

Ich habe die 8 Datenleitungen auf ein Port (Port A) des AVR geführt. Die Management-Leitungen habe ich erstmal ignoriert und die drei Handshake-Leitungen auf weitere freie Pins des AVR geführt. Somit habe ich 11 Leitungen verwendet.

Software

Das spezielle Drei-Draht-Handshake des GPIB-Busses ist an verschiedenen Stellen im Internet beschrieben (z.B. hier). Der Handshake läuft wie folgt ab:

  1. Listener signalisieren mit NRFD (Not Ready For Data), wenn sie Daten empfangen können (sie setzen NRFD auf HIGH). Wenn der letzte Listener HIGH setzt, geht NRFD auf HIGH.
  2. Talker bemerkt dies und setzt Daten auf die Datenleitungen und zieht DAV (Data Valid) auf LOW.
  3. Listener bemerken dies und setzen für die Zeit des Lesens der Daten NRFD auf LOW. Wenn ein Listener mit dem Lesen fertig ist, setzt er NDAC auf HIGH. Wenn der letzte Listener fertig ist, geht dadurch NDAC auf HIGH. Dies bemerkt der Talker und setzt DAV auf HIGH. Daten sind nicht mehr gültig.
  4. Nach einer gewissen Zeit setzen die Listener NDAC wieder auf LOW, und NRFD auf HIGH so dass der Zyklus für das nächste Byte beginnen kann.

Dieses Protokoll habe ich zunächst in einer Schleife mittels Polling implementiert. Es wurden zwar Daten gelesen, aber dasselbe Datum gleich mehrfach, ich habe den Übergang DAV nach HIGH im Schritt 3 einfach nicht mitbekommen, egal was ich probiert habe. Es ging soweit, dass ich glaubte, dass in der TALKER/Listener-Konfiguration DAV immer LOW bleibt. Nachmessen mit dem Oszilloskop hat aber gezeigt, dass DAV sehr wohl zwischen HIGH und LOW wechselt, und zwar mit etwa 3300Hz. Das Oszilloskop sendete im Versuch den Bildschirminhalt, etwa 3000 Bytes. Möglicherweise ist Polling hier zu langsam (siehe Bemerkung weiter unten, es war ein Programmierfehler, Polling ist nicht zu langsam).

Ich habe die Software daher verändert, und zwar so, dass auf der fallenden und steigenden Flanke des DAV-Signals (das an INT0 anliegt) ein Interrupt ausgelöst wurde. Diese Interrupts habe ich zunächst einfach gezählt. Es waren etwa 6000 Interrupts, was gut zu den etwa 3000 Zeichen passte. Ich habe dann die Implementierung ganz auf Interrupts umgestellt, so dass die Zeichen in der Interrupt-Service-Routine gelesen werden. Danach wurden alle Zeichen sauber übertragen.

Im folgenden ist die zentrale Interrupt-Routine dargestellt. Da die Daten in negativer Logik ankommen, werden sie mit 0xff per XOR verknüpft, um das echte Zeichen zu erhalten. Die „dav“-Variable verwende ich außerhalb der ISR, um festzustellen ob ein neues Zeichen eingetroffen ist. Dort wird sie auch zurückgesetzt:

ISR(INT0_vect) {
   if (irq_falling_edge==1) {
         
      // handshake: clear NRFD, means i am busy now to read data
      PORTD &= ~_BV(G_NRFD);         
      // read data
      byte = PINA ^ 0xff;
      // handshake: set ndac, means i have completed/accepted the read
      PORTD |= _BV(G_NDAC); 
      // set data valid flag
      dav=1;
   
      irq_falling_edge=0; // wait for next raise
      MCUCR = (1<<ISC00)|(1<<ISC01); // raise int0 on rising edge
   } else {
      // handshake: clear ndac (this is a prerequisite for receive next byte)
      PORTD &= ~_BV(G_NDAC);
   
      irq_falling_edge=1; // wait for next fall
      MCUCR = (1<<ISC00); // raise int0 on falling edge
   }
}

Ein weiteres Problem ist, dass ich das Ende der Übertragung zunächst nicht erkannte. Der GPIB-Bus nutzt hierfür die EOI-Leitung, die der Talker beim Übertragen des letzten Bytes auf LOW zieht. Diese Leitung hatte ich aber nicht verwendet. Das Oszilloskop kann jedoch zusätzlich am Ende einer Übertragung ein CR schicken. Dies habe ich am Oszilloskop eingestellt, danach konnte ich auch das Ende der Übertragung feststellen. Für Binärübertragung muss man allerdings den Weg über die EOI-Leitung gehen.

Im folgenden ist die so gewonnene Ausgabe eine WAVEFORM dargestellt. Es handelt sich um ein Rechtecksignal. Die momentane Einstellung der Y-Achse von Kanal 1 und der Zeitachse sowie weitere Einstellungen werden als Präambel (WFRMPRE) vorangeschickt. Danach folgt ab „CURVE“ die Waveform-Beschreibung. Werte um 50 sind das HIGH, Werte um 0 sind das LOW des Signals. Die Ausgabe wird als eine lange Zeile geschickt, ganz am Ende kommt ein CR-Zeichen. Der Übersichtlichkeit halber habe ich die Ausgabe in mehrere Zeilen aufgeteilt.

WFMPRE WFID:"CH1 DC  20mV  10ms NORMAL",NR.PT:1024,PT.OFF:512,PT.FMT:Y,XUNIT:SEC,XINCR:2.000E-4,YMULT:
8.000E-4,YOFF:0,YUNIT:V,BN.FMT:RI,ENCDG:ASCII;
CURVE 0,0,-1,0,0,1,-1,0,0,0,1,1,49,50,50,50,49,49,50,51,49,50,50,50,50,51,50,50,50,50,50,50,
50,51,50,50,50,52,50,52,50,49,51,51,50,50,50,49,50,50,51,52,50,52,49,50,50,51,50,50,51,50,0,
0,1,1,-1,0,0,0,-1,0,-1,-1,-1,0,0,-2,0,0,0,0,0,-1,-1,1,0,0,-1,0,1,0,1,0,1,-1,1,1,-1,0,1,0,0,1,1,0,1,
0,-1,1,-2,0,50,51,50,50,49,51,52,50,51,51,50,50,51,50,52,49,51,52,51,50,51,50,52,50,51,52,50,
50,49,51,50,51,50,50,51,50,52,49,51,51,51,51,49,50,52,51,50,50,51,51,2,0,1,-2,0,-1,1,-1,0,1,0,
0,0,-1,-1,1,0,-1,1,0,0,-1,0,-2,1,0,0,-2,0,0,-1,-1,0,1,-1,-1,-1,0,0,-1,-2,-1,-1,1,-1,1,0,1,-2,-1,49,49,
48,51,50,50,50,50,49,52,51,51,52,50,50,51,49,49,51,50,51,50,50,49,50,51,51,51,51,51,51,50,50,
50,52,51,51,50,50,50,51,50,51,52,52,51,51,52,50,50,1,-1,0,0,1,0,-1,-1,1,-1,1,-1,-1,0,0,1,0,0,-1,
-1,-2,-1,1,0,0,-1,1,-1,1,-2,-1,-1,-1,-1,0,1,1,1,-1,1,-1,1,-1,-2,0,-1,-1,-2,1,0,50,50,50,50,50,50,50,
51,51,50,51,50,50,50,49,51,51,50,50,51,51,50,51,50,52,52,50,51,50,50,51,50,50,51,50,50,51,50,
51,51,51,50,50,51,49,49,51,49,49,50,0,0,0,-1,0,-1,1,0,0,-1,0,0,-1,0,0,1,-1,0,-2,0,0,0,-1,-1,-1,-1,
0,0,0,-1,-1,0,-2,0,-1,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-2,-1,0,49,50,49,50,49,51,50,50,51,50,51,50,50,49,
50,52,52,49,48,51,51,50,50,51,51,51,50,50,51,52,50,50,51,51,50,51,51,50,49,51,50,50,52,50,52,
50,49,50,50,50,0,0,0,0,0,0,-1,0,0,0,0,0,0,-1,-1,-1,0,1,1,-1,-1,0,0,0,-1,-2,0,0,1,0,-1,1,1,0,0,1,-1,-1,
0,-1,0,-1,2,0,0,0,-3,0,1,0,50,50,51,49,51,52,50,49,50,51,50,50,50,51,51,50,51,51,51,52,49,50,49,
50,50,49,51,53,49,51,50,48,51,51,51,50,49,51,51,49,51,50,52,51,50,50,50,51,52,50,-1,-1,0,0,-2,-1,
-1,-1,0,-2,-1,-1,-1,0,0,-2,0,-1,0,-1,-1,-1,0,-1,-1,0,0,-2,0,0,-1,1,0,-3,0,0,-2,0,-1,-1,0,0,-1,0,0,0,-2,-1,
0,-1,50,50,51,49,51,49,50,51,50,50,50,50,49,51,51,50,51,51,50,51,50,49,50,51,50,50,49,50,52,50,51,
50,49,50,51,51,50,51,50,52,50,51,52,50,50,50,50,51,51,51,-1,0,0,-1,0,0,0,0,-1,0,0,1,-2,0,0,-1,0,0,0,0,
-1,0,-1,-1,-1,0,0,0,1,-1,0,0,-1,0,-1,0,-1,-1,1,1,0,0,-1,0,-1,-1,-1,-1,1,0,50,51,50,51,51,50,50,51,50,51,49,
52,50,50,50,51,50,51,49,51,52,50,51,50,51,50,51,52,48,49,49,50,48,49,50,51,50,50,50,50,50,50,51,49,
50,51,48,50,51,52,0,0,0,0,0,0,-2,0,0,0,0,-1,-1,0,-1,0,0,-1,0,1,0,-1,-1,-2,0,0,0,0,0,-1,1,0,0,-1,0,-1,-1,1,
0,0,0,-1,0,-1,0,0,0,-1,-1,-2,49,50,50,52,50,49,50,50,49,50,52,50,49,50,51,52,49,49,49,51,51,50,50,50,50,
49,51,50,50,50,51,52,50,50,50,50,50,50,49,52,51,50,50,50,51,52,50,50,52,49,0,-1,0,-1,0,1,2,-1,-1,-1,0,1,
0,-1,0,0,1,0,0,-1,0,1,0,0,-2,0,-1,-1,0,-1,-1,-1,0,-1,0,-1,0,-1,0,0,1,-1,-2,-1,-2,1,-1,-1,1,1,50,48,49,49,49,
50,50,51,50,51,50,50,52,50,51,52,51,51,52,50,52,49,50,49,51,49,52,50,50,50,48,51,50,50,49,51,51,50,51,
50,50,48,50,52,51,49,50,50,49,51,0,-2,-1,-1,1,0,0,0,0,1,-1,-2,0,1,0,-2,-1,0,0,0,-1,-1,0,0,0,0,-2,1,0,-1,-1,-1,
-1,0,-2,-1,-1,0,-1,-1,-1,-1,0,-1,0,-2,0,0,1,-2,49,50,51,51,49,50,52,51,50,51,52,51

Im Ergebnis kann man mit dieser Konstellation Daten, die das Oszilloskop schickt, sauber zum PC übertragen. Man kann aber leider keine Kommandos zum Oszilloskop schicken. Dies würde die Erweiterung des Beispielcodes um Controller-Funktionen und die Verwendung zusätzlicher Leitungen erfordern. Außerdem muss man dann das Thema „Open Collector“ lösen, sonst geht entweder der ATMega32 oder aber das GPIB-Interface des Oszilloskops kaputt.

Ein Versuch mit HPGL-Ausgabe, die das Oszilloskop auch beherrscht, zeigt ein neues Problem: Statt in einer langen Zeile kommen die HPGL-Daten zeilenweise. Meine Erkennung des Übertragungsendes funktioniert damit nicht mehr.

Hier beispielhaft die HPGL-Ausgabe (nicht komplett):

in;sc-690,689,-512,511;
sp1;
pa-500,-400;pd;pr1000,0,0,800,-1000,0,0,-800;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl1;xt;pr100,0;
tl0,1;yt;pr0,100;
tl0,1;yt;pr0,100;
tl0,1;yt;pr0,100;
tl0,1;yt;pr0,100;
tl0,1;yt;pr0,100;
tl0,1;yt;pr0,100;

Ich habe eine HPGL-Ausgabe mit dem Tool „hp2xx“ in ein JPEG überführt. Die Ausgabe kann man in kermit mittels „log session <datei>“ in eine Datei umleiten. Das hpxx-Kommando konvertiert mit folgender Abfolge:

rm -f tmp.eps out.pdf
./hp2xx -p 22222222 -C -o 20 -O 150 -h 100 -a 2  -m eps -d 600 $1 -f tmp.eps
ps2pdf tmp.eps out.pdf
acroread out.pdf

Die Kurve und die Skala ist ok, die Beschriftung kommt viel zu klein heraus. Mit der Option „-d xxx“ kann man die DPI-Auflösung des Ergebnisbildes beeinflussen, ab 300 dpi kann man den Text lesen.

Versuche mit dem Tool hpgl2ps waren schlechter als die mit hp2xx.

hp2xx Output

Elaborierterer Ansatz: Talker/Listener Szenario

Als nächstes habe ich zusätzlich die Leitungen EOI, ATN, SRQ, IFC und REN an den AVR geführt. Die Beschaltung sieht dann tabellarisch wie folgt aus:

GPIB Pin Name  GPIB-Buchse Pin Nummer  ATMega32 Pin Name  AVR Board Buchse Pin Nummer
DIO1..8 (Datenpins) 1..4 und 13..16 PortA0..PortA7  1..8
EOI 5 PD4 30
DAV 6 PD2 28
NRFD 7 PD3 29
NDAC 8 PD5 31
SRQ 10 PD6 32
ATN 11 PD7 33
IFC 9 PB0 9
REN 17 PB1 10
GND 23 GND 39

Nutzung des EOI-Signals

Eine Untersuchung des EOI-Signals mit einem zweiten Oszilloskop ergab, dass das Signal ohne Übertragung LOW ist, während der kompletten Übertragung auf HIGH gehalten wird und nach dem letzten Zeichen wieder auf LOW geht. Dies unterscheidet sich von der GPIB-Dokumentation, die ich im Internet gefunden habe. Dort wird beschrieben, dass das EOI-Signal nur genau für das letzte Zeichen einer Übertragung  LOW gezogen wird. Entweder funktioniert mein Oszilloskop anders als der Standard oder ich lese die Beschreibungen nicht richtig.

Beschaltung der Pins des AVRs für Open Collector

Die AVR Controller sind zwar extrem flexibel, man kann aber die Port-Pins nicht direkt in eine Open Collector Modus schalten. Man könnte nun Open-Collector-fähige Bustreiber zwischenschalten, aber dafür bin ich zu faul. Eine Suche im Internet ergibt, dass man scheinbar die Open-Collector-Schaltung simulieren kann, indem man auf den Pin ein LOW schreibt und dann mittels des DDRx-Registers den Pin umschaltet. Die Idee bedeutet im einzelnen(am Beispiel PORTD und Pin PD4):

Funktion „normale“ Beschaltung Open Collector Simulation
Initialisierung des Pins DDRD |= _BV(PD4) // als Ausgang
DDRD &= ~BV(PD4) // als Eingang
PORTD &= ~_BV(PD4)
DDRD &= ~_BV(PD4)
Pin auf HIGH setzen PORTD |= _BV(PD4) DDRD &= ~_BV(PD4)
Pin auf LOW setzen PORTD &= ~_BV(PD4) PORTD &= ~_BV(PD4)
DDRD |= _BV(PD4)
Pin lesen unsigned char p = PIND & _BV(PD4) DDRD &= ~_BV(PD4)
unsigned char p = PIND & _BV(PD4)

Nach Umstellung funktioniert mein Code noch, die Simulation scheint also zu funktionieren. Als nächstes steht die Implementierung der Talker und Controller-Funktionalität an.

Nach einigem Hin- und Her bin ich völlig von der Interrupt-gesteuerten Leseroutine abgekommen. Die Probleme, die ich beim Pollen hatte, waren auf einen Fehler meinerseits (Lesen von PORTx statt von PINx, was nicht funktioniert) zurückzuführen. Nach der Implementierung der Lesefunktion gpib_read() und der Schreibfunktion gpib_write() stand die Implementierung der Controllerfunktionalität an.


Das Experimentierboard von Pollin. An die 40-polige Buchse (IDE-Buchse) ist ein altes IDE-Kabel angeschlossen, an den der GPIB-Stecker angelötet wurde.

GPIB Controller Implementierung

Der Controller übernimmt am Anfang den Bus  und steuert alle Geräte am Bus, d.h. er definiert sie als Listener, Talker oder „Taub“, also nicht am Bus partizipierend. Dazu nutzt er Kommandoleitungen (wie IFC und REN) und Bus-Kommandos (wie DCL, UNL, UNT). Ein Kommandobyte auf dem Bus unterscheidet sich von einem Datenbyte dadurch, dass der Controller die ATN-Leitung während des Sendens von Kommandobytes auf logisch 0 zieht.

Geräte werden durch das Ansprechen über ihre Listener-Adresse (=Geräteadresse + 0x20) zu Listenern und durch das Ansprechen über ihre Talker-Adresse (=Geräteadresse +0x40) zu Talkern. Es darf immer nur einen Talker geben, aber mehrere Listener. Der Controller hat üblicherweise, aber nicht zwingend, die Geräteadresse 0x00. Kommandos setzt der Controller mit der Funktion gpib_cmd() ab.

Meine simple Implementierung des Controllers sieht wie folgt aus:

  1. Übernahme des Busses durch REN und IFC (Funktion gpib_init() und gpib_controller_assign()), dann Senden von DCL (Funktion gpib_cmd()). Alle Geräte gehen dadurch in einen definierten Anfangszustand.
  2. Ausführen einer Endlosschleife:
    1. Kommandos werden von STDIN gelesen. Ein CR zeigt das Kommandoende an. Der Controller schaltet sich als Talker und und ein weiteres Gerät als Listener (z.B. mein Oszilloskop mit Geräte-Adresse 0x01). Das Kommando wird auf den Bus gelegt (gpib_cmd() + gpib_write()). Das Kommandoende wird über die Leitung EOI angezeigt (ein reale Controller würde sicher komplexer vorgehen…).
    2. Die Listener lesen das Kommando und führen es aus. Bei Kommandos ohne Antwort geht der Controller wieder in die Schleife, um neue Kommandos zu lesen. Bei Kommandos mit Antwort schaltet der Controller sich als Listener und ein weiteres Gerät (mein Oszilloskop) als Talker. Die Antwort wird vom Gerät gelesen (gpib_read()). Das Gerät zeigt das Ende der Antwort über die Leitung EOI an. Der Controller gibt jedes gelesene Zeichen auf STDOUT aus. Nach Ausgabe der Antwort geht der Controller wieder in die Schleife, um neue Kommandos zu lesen.
      Der Controller unterscheidet Kommandos mit und ohne Antwort anhand des Zeichens „?“ am Ende des Kommandos (stimmt nicht immer…).
  3. Asynchron Verarbeitung neben der Endlosschleife in Punkt 2.: Falls ein Gerät von sich aus, also ohne Einladung durch den Controller, am Bus aktiv werden will, erzeugt es einen Service Request SRQ (Ziehen der SRQ-Leitung auf logisch 0). Der Controller bekommt dies mit, ermittelt mittels eines Serial Polling genannten Vorgehens den Erzeuger des SRQs und liest dessen Status ein. Der Status wird auf STDOUT ausgegeben (Funktion gpib_serial_poll()). Serial Polling bedeutet, dass der Controller mit dem Kommando SPE (Serial Polling Enabled) das Polling einleitet. Alle Geräte werden dann hintereinander als Talker definiert, der Controller definiert sich als Listener und liest Daten vom Talker ein. Beim seriellen Polling gibt jeder Talker nur genau 1 Byte, sein Status Byte aus. Das SRQ-erzeugende Gerät hat das Bit 6 im Statusbyte gesetzt. Damit weiß der Controller, welches Gerät den Request erzeugt hat. Der Controller beendet mit SPD (Serial Polling Disable) das Polling, alle Geräte verhalten sich von da ab wieder „normal“. Das Behandeln des SRQs ist gerätespezifisch. Mein Oszilloskop teilt mit dreistelligen Errorcodes, wie z.B. „157“ Fehler bei der Kommandoerkennung („vertippt“) und ähnliches mit.
  4. Theoretisch gibt der Controller am Ende den Bus wieder frei (Funktion gpib_controller_release()).

Beim Handshake kann man den Controller mittels busy waiting auf das Ändern einer Handshake-Leitung warten lassen. Falls die Änderung nie kommt, weil z.B. das Gerät abgeschaltet wurde oder das Gerät den Handshake nicht richtig mitmacht, wartet der Controller dann ewig. Ich habe daher diese busy-waiting-Schleifen daher durch Schleifen ersetzt, die nach einem Timeout (5s) abbrechen. Damit bleibt der Controller auch in diesen Fällen benutzbar.

Im folgenden ist eine Session dargestellt. Wenn das Oszilloskop eingeschaltet wird und bereit ist, generiert es einen SRQ. Der Controller fragt bei der SRQ-Behandlung mittels „EVENT?“ den SRQ-Grund ab, es kommt die Antwort „401“. 401 bedeutet laut Oszi-Handbuch „2432A was just powered on.“. Danach folgt die User Eingabe „id?“ auf die das Gerät mit der Ausgabe seiner Typenbezeichnung antwortet. Danach werden ein paar Kommandos abgesetzt, die keine Antwort liefern:

INIT
MEASUREMENT WINDOW:ON
CURSOR FUNCTION: TIME,TARGET:CH1,UNITS:BASE
CURSOR TPOS:ONE:200,TPOS:TWO:500

Zum Schluss wird mittels „CH1?“ die aktuelle Konfiguration des Kanals 1 abgefragt.

dennis@soc:~> kermit -c
Connecting to /dev/ttyS2, speed 19200
 Escape character: Ctrl- (ASCII 28, FS): enabled
Type the escape character followed by C to get back,
or followed by ? to see other options.
----------------------------------------------------
 
SRQ detected.
status byte from device 0x41 = 0x41 (char=A)
SRQ emitter is device = 0x41
 
command: EVENT?
Query. Will check for answer.
EVENT 401
> id?
command: id?
Query. Will check for answer.
ID TEK/2432A,V81.1,"24-DEC-89  V2.30 /2.5"
> INIT
command: INIT
Command only.
> MEASUREMENT WINDOW:ON
command: MEASUREMENT WINDOW:ON
Command only.
> CURSOR FUNCTION: TIME,TARGET:CH1,UNITS:BASE
command: CURSOR FUNCTION: TIME,TARGET:CH1,UNITS:BASE
Command only.
> CURSOR TPOS:ONE:200,TPOS:TWO:500
command: CURSOR TPOS:ONE:200,TPOS:TWO:500
Command only.
> CH1?
command: CH1?
Query. Will check for answer.
CH1 VOLTS:1E-1,VARIABLE:0,POSITION:0,COUPLING:DC,FIFTY:OFF,INVERT:OFF
>

Fazit: Insgesamt funktioniert somit die Implementierung eines Controllers mit dem AVR komplett in Software recht gut. Für einen echten Praxis-Einsatz müsste man die Software noch stark erweitern und vor allen auch mit anderen Geräten testen.

Hier die Software unter GNU General Public Licence als ZIP-Datei. Die ZIP-Datei enthält auch die Dateien uart.h/uart.c von Peter Fleury für die RS232 Kommunikation, die ebenfalls unter der GNU GPL steht.

ZIP-File mit allen Dateien:  gpib-0.66.zip

Versionshistorie:
0.1: Kommunikation mit einem Gerät (Tek 2432 getestet) ok.
0.63: Kommunikation mit 2 Geräten (und vermutlich auch mit mehr als 2) möglich. Fehler in Lesefunktion entfernt.
0.66: doxygen Dokumentation hinzugefügt

Eine Aufzeichnung einer kompletten Session mit 2 Geräten ist hier zu finden: Session mit 2 Geräten mit Code Version 0.63. Die verwendeten Geräte waren ein Oszilloskop Tektronix 2432A und ein Logik Analysator Tektronix 1241. Bei den Geräten wird die ID? abgefragt, beim Oszilloskop die momentane Konfigurationen der beiden Kanäle CH1? und CH2?, die Konfiguration für die Darstellung der aktuellen angezeigten Kurve WFMPRE? und dann die Kurve selbst (CURVE?). Das Datenformat wird dabei von Binary nach ASCII verändert. Danach wird der Logic Analysator angesprochen und mit REFMEM? der momentane Inhalt des Referenzspeichers ausgelesen.

Aufbau des Geräts

Im folgenden ein paar Bilder zum aufgebauten Gerät.

 

Weiterführende Links