Linux, PC und Hardware


Prof. Jürgen Plate

Standard-Schnittstellen

Die serielle Schnittstelle

Bei einem seriellen, asynchronen Datentransfer werden die einzelnen Bits eines Datenbytes nacheinander über eine Leitung übertragen (Bild ). Der Ruhezustand der Übertragungsleitung, der auch mit "Mark" bezeichnet wird, entspricht dem Pegel einer logischen 1. Die zur Übertragung verwendeten Spannungs- bzw. Strompegel können Sie der Beschreibung der einzelnen Schnittstellen entnehmen. Die Übertragung eines Bytes beginnt mit einem vorangestellten Startbit, das als logische 0 ("SPACE") gesendet wird. Anschließend werden nacheinander - je nach eingestelltem Format - fünf bis acht Datenbits, beginnend mit dem niederwertigen Bit (least significant bit, LSB), ausgegeben. Dem letzten Datenbit kann ein Paritätsbit folgen, das zur Erkennung von Übertragungsfehlern dient. Das Paritätsbit ergänzt das Datenbyte auf eine gerade (gerade Parität, even parity) oder ungerade (ungerade Parität, odd parity) Anzahl von 1-Bits. Das Ende des Zeichens wird durch ein oder zwei Stoppbits gebildet. Alle Bits werden sequenziell gesendet.

Die Datenrate wird in Bit pro Sekunde (bps) bzw. Baud (nach dem französischen Ingenieur und Erfinder Jean Maurice Émile Baudot) angegeben. Dabei werden alle Bits (auch Start- und Stoppbit) gezählt und Lücken zwischen den Bytetransfers ignoriert. Deshalb ist die Baudrate der reziproke Wert der Länge eines Bits. Als Datenraten sind folgende Werte üblich:

150, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600 und 115200

Nach dem Stoppbit kann sofort wieder eine neue Übertragung mit einem Startbit beginnen. Zur Vermeidung von Datenverlusten muss der Empfänger die Datenübertragung anhalten können, wenn keine weiteren Daten mehr verarbeitet werden können. Dieses sogenannte Handshake kann auf zwei Arten realisiert werden:

Da die Pause zwischen zwei aufeinanderfolgenden Datenbytes beliebig lang sein darf, spricht man von einer "asynchronen" Kommunikation. Für den Datenverkehr synchronisieren sich Sender und Empfänger bei der asynchronen Übertragung für jedes einzelne Zeichen neu. Vor jedem Zeichentransfer liegt auf der Übertragungsleitung das Signal auf 1-Pegel. Soll nun ein Zeichen übertragen werden, so wird dies dem Empfänger vom Sender durch ein Startbit angezeigt, indem für einen Taktzyklus das Signal auf 0 gelegt wird. Anhand der 0-1-Flanke kann der Empfänger den Beginn eines Datenbytes exakt erkennen. Damit sind Sender und Empfänger für dieses Zeichen synchronisiert. Anhand der Stoppbits erkennt der Empfänger das Ende des Zeichens, damit dient das Stoppbit ebenfalls der Synchronisation. Sender und Empfänger müssen sich zuvor auf die Anzahl der Stoppbits, der Datenbits, der Berechnung der Paritätsbits und auf die Frequenz des Übertragungstaktes (Baudrate) verständigen. Diese Parameter werden zumeist einmal in den Schnittstellen einprogrammiert und bleiben für die gesamte Dauer der Kommunikation unverändert.

Die serielle PC-Schnittstelle

Auch wenn wir für die Programmierung auf die seriellen Treiber von Linux zurückgreifen können und nicht in Assembler programmieren müssen, sind einige Grundkenntnisse über die Schnittstellenhardware angebracht. Das Herzstück der seriellen Schnittstelle im PC ist der serielle Baustein UART 8250 (Universal Asynchronous Receiver and Transmitter) bzw. dazu kompatible Schaltungen (zeitweise war der Baustein 16550 sehr beliebt, da er einen kleinen FIFO-Speicher enthielt, der bei den damals noch recht langsamen Prozessoren einen Datenverlust beim Empfang verhinderte). Dieser Baustein erlaubt die serielle Datenübertragung und übernimmt dabei die Datenumwandlung von parallel nach seriell und umgekehrt. Der 8250-Baustein verfügt über zehn interne Register für die Einstellung von Übertragungsparameter, Leitungssteuerung und das Senden und Empfangen von Daten (siehe Tabelle). Der größte Teil dieser Register wird bei der Initialisierung des Bausteins verwendet, während bei der Datenübertragung selbst meist nur ein bis zwei Register zum Einsatz kommen. Es sind im Grundausbau des PC maximal vier serielle Schnittstellen möglich, die unter Windows mit COM1 bis COM4 bezeichnet werden und unter Linux über die Gerätenamen /dev/ttyS0 bis /dev/ttyS3 angesprochen werden. Mit entsprechender Zusatzhardware (sogenannten Multiseriell-Karten) können auch mehr Schnittstellen bedient werden. Beim Original-PC lassen sich aber nur die beiden ersten per Interrupt betreiben, alle weiteren müssen über Polling abgefragt werden. Bei Verwendung von Seriell-Steckkarten wird auch oft ein Interrupt-Sharing eingesetzt. Die Portadressen sind für /dev/ttyS0 3F8-3FE, für /dev/ttyS1 2F8-2FE, für /dev/ttyS2 3E8-3EE und für /dev/ttyS3 3E0-3E6.

PortFunktionBem.
Base Sendedaten (TDR) Empfangsdaten (RDR) (1)
Base Baudrate (niederwertiges Bit) (2)
Base+1 Interrupt Enable Register (IER) (1)
Base+1 Baudrate (höherwertiges Bit) (2)
Base+2 Interrupt ID (IID)
Base+3 Line Control
Base+4 Modem Control
Base+5 Line Status
Base+6 Modem Status

(1) Bit 7 im Line Control Register = 0}
(2) Bit 7 im Line Control Register = 1}

Das folgende Bild zeigt die Belegung des Line-Control-Registers, wobei mit DLAB das Bit 7 des Line-Control-Registers bezeichnet wird (DLAB = Divisor Latch Access Bit). Die Registeradressen beziehen sich auf die jeweilige Basisadresse der Schnittstelle.

Das Modem-Control-Register dient zur Steuerung der zusätzlichen Steuerleitungen einer seriellen Schnittstelle. Für uns interessant sind die Leitungen RTS und DTR, weil diese auf dem Stecker herausgeführt sind und als digitale Steuerleitungen auch für andere Zwecke genutzt werden können. Die Leitung OUT1 wird im PC nicht verwendet, und die Leitung OUT2 dient der internen Interrupt-Freigabe. Ist kein Modem angeschlossen, werden die Leitungen DTR und RTS meist konstant auf 1 gesetzt. Soll die serielle Schnittstelle im Interruptbetrieb arbeiten, ist Bit 3 (OUT2) auf 1 zu setzen. Der Loop-Modus ist nur für Tests interessant. Ist das Bit 4 gesetzt, verhält sich die Schnittstelle so, als seien Sendeausgang und Empfangseingang direkt miteinander verbunden.

Im Interruptbetrieb muss für die Schnittstelle auch eine IRQ-Leitung existieren, über die der Interrupt ausgelöst werden kann. Deshalb ist zusätzlich noch eine Programmierung des Interrupt-Controllers 8259 im PC notwendig. Beim Interrupt-Control-Register gilt, dass eine 0 den entsprechenden Interrupt sperrt und eine 1 ihn freigibt. Glücklicherweise wird aber auch dies vom Linux-Schnittstellentreiber erledigt.

Für Interessierte sei hier in Stichpunkten skizziert, was nötig ist, um eine selbst geschriebene Interrupt-Routine zu aktivieren, die Zeichen vom 8250 holt und in einem Puffer speichert:

  1. Empfangspuffer einrichten
  2. Baudrate einstellen
  3. Interruptvektor (z. B. IRQ4) auf die eigene Interrupt-Routine zeigen lassen
  4. Dummy-Read auf das Datenregister ausführen, um ein eventuell gesetztes Interrupt-Bit für "Daten-Empfangen" zu löschen.
  5. Am Interrupt-Controller 8259 den Interrupt (z. B. IRQ4) freigeben.
  6. Interrupt am Schnittstellenbaustein 8250 freigeben: Bit 0 des Interrupt-Control-Registers auf 1 setzen und den Ausgang OUT2 freischalten.
Die Interrupt-Serviceroutine wird immer dann aufgerufen, wenn der 8250 ein Byte empfangen hat. Sie muss Folgendes erledigen:

  1. Empfangenes Byte vom 8250 abholen (Datenregister) und im Pufferbereich speichern.
  2. "End of Interrupt" an den Interrupt-Controller melden.
Da mehrere Ereignisse nur einen Hardware-Interrupt auslösen, kann die Interrupt-Routine über das Interrupt-Identification-Register Auskunft darüber erhalten, welches Ereignis die Unterbrechung ausgelöst hat. Treten mehrere Ereignisse gleichzeitig auf, müssen sie von der Interrupt-Routine nacheinander abgearbeitet werden. In diesem Fall entscheidet die Priorität des Interrupts, welches Ereignis als Erstes behandelt wird. Bit 0 gibt Auskunft darüber, ob ein Interrupt aufgetreten ist (1 = kein Interrupt, 0 = Interrupt hat stattgefunden). Es bleibt so lange 0, bis alle entsprechenden Ereignisse abgearbeitet wurden. Die Bits 2 und 3 spezifizieren das Ereignis (Priorität in Klammern):

00 Wechsel im Modem-Status (Eingangsleitungen, Priorität 4)
01 Sende-Register leer (Sende-Interrupt, Priorität 3)
10 Empfang eines neuen Bytes (Priorität 2)
11 Overrun-, Framing- oder Parity-Error bzw. Break-Signal empfangen (Priorität 1).

Die Übertragungsgeschwindigkeit der seriellen Schnittstelle wird mittels der beiden Baudrate-Register eingestellt. Dabei wird der Teilerwert (1 Word) über die ganzzahlige Division

Teiler = 115200/Baudrate
ermittelt und der LSB-Teil des Teilerwerts ins Baud-Rate-Register LSB (Basisadresse, DLAB = 1) und der MSB-Teil ins Baud-Rate-Register MSB (Basisadresse +1 , DLAB = 1) geschrieben. Für die Initialisierung auf 9600 Baud ergibt beispielsweise die obige Formel 115200 / 9600 = 12 = 0CH. Damit wird das Baud-Rate-Register LSB mit 0CH und das Baud-Rate-Register MSB mit 00H initialisiert.

Den Status der Eingangsleitungen des Bausteins liefern die Statusregister für Modem und Leitungen:

Der Baustein 8250 liefert TTL-Signale an seinen Ausgangspins. Für die "Außenwelt" werden aber meist andere Signalpegel verwendet. Deshalb sollen nun die gebräuchlichsten seriellen Schnittstellen mit ihren Signalpegeln besprochen werden. Vom seriellen Datenformat und von der Programmierung her gibt es dabei keine Unterschiede. Am PC finden wir standardmäßig die RS232-Schnittstelle. Für andere Signalpegel oder auch für die Weiterverarbeitung der Signale muss das angeschlossene Gerät gegebenenfalls einen Schnittstellenwandler enthalten.

Die RS232C-Schnittstelle (V.24)

Diese häufig verwendete Schnittstelle nach der amerikanischen Norm RS232C ist in Europa mit fast identischen Eigenschaften unter V.24 genormt. Dieser Standard ist für zwei Kommunikationsgeräte konzipiert, die beide je eine Datenquelle (transmit, TX) und eine Datensenke (receive, RX) besitzen können. Zur bidirektionalen Datenübertragung werden mindestens drei Leitungen benötigt: eine Sendeleitung (TXD), eine Empfangsleitung (RXD) und eine Masseleitung (Ground). Die Signale der RS232 sind bipolar ausgelegt. Eine logische 0 wird bei den Datenleitungen durch eine Spannung von +3 bis +15 Volt, eine logische 1 durch -3 bis -15 Volt dargestellt. Bei den Steuerleitungen sind die Pegel genau umgekehrt (positive Spannung = 1, negative Spannung = 0). Das Signal-Störverhältnis ist damit wesentlich größer als bei TTL- oder CMOS-Pegeln. Dies ermöglicht eine störungsfreie Übertragung über größere Entfernungen. Die maximale Entfernung zwischen RS232-Geräten ist wie bei allen seriellen Übertragungsverfahren stark vom verwendeten Kabel und der Datenrate abhängig. Laut EIA-Norm definiert die RS232C die maximale Entfernung mit 15 Metern. Bei Verwendung von kapazitätsarmen Kabeln kann die maximale Distanz bis zu 50 Meter betragen. Je länger ein Kabel ist, umso größer gilt die Problematik der Potentialdifferenz zwischen beiden Endpunkten. Mit wachsenden Kabellängen sowie im industriellen Umfeld sollte grundsätzlich eine galvanische Trennung eingesetzt werden, damit unliebsame Störungen vermieden werden.

Neben der Masseleitung und den Datenleitungen gibt es noch eine ganze Reihe von Leitungen, die den Verkehr zwischen Rechner und Peripherie steuern. Meist interessieren aber nur einige Leitungen, um den Verkehr zwischen Computer und Peripherie oder zwischen zwei Computern aufrechtzuerhalten. Die anderen Leitungen bleiben unbeschaltet oder werden auf einen festen Pegel gelegt. Die wichtigsten Leitungen sind:

Damit sind die sechs wichtigsten Leitungen aufgeführt. Oft sind noch die Leitungen DTR und DCD belegt, die dann meist auf die entsprechenden Anschlüsse des Schnittstellenbausteins führen. Die Norm definiert noch etliche weitere Signale, die am PC keine Entsprechung besitzen. Tabelle liefert Aufschluss über Signalbezeichnungen, Steckerbelegungen (9-poliger und 25-poliger Stecker) sowie Signalbezeichnung der RS232-Schnittstelle.

ITUDINUS25-pol.9-pol.BeschreibungRicht.
101 E 1 - 1 -Schutzerde -
102 E 2 GND 7 5Signalerde (Ground) -
103 D 1 TXD 2 3Sendedaten (Transmit Data)
104 D 2 RXD 3 2Empfangsdaten (Receive Data)
105 S 2 RTS 4 7Sendeteil einschalten (Request To Send)
106 M 2 CTS 5 8Sendebereitschaft (Clear To Send)
107 M 1 DSR 6 6Betriebsbereitschaft (Data Set Ready)
108.2S 1.2 DTR20 4Terminal ist bereit (Data Terminal Ready)
109 M 5 DCD 8 1Empfangspegel (Data Carrier Detect)
125 M 3 RI 22 9Ankommender Ruf (Ring Indicator)

Die Richtungsangabe bezieht sich auf die Peripherie ( bedeutet Peripherie nach PC, entsprechend PC nach Peripherie). Werden Peripherie/Modem (DÜE = Datenübertragungseinrichtung) und Computer (DEE = Datenendeinrichtung) miteinander verbunden, verwendet man ein Kabel mit einer 1:1-Verbindung der wichtigsten Leitungen.

Sollen hingegen zwei Computer direkt, also ohne Modem miteinander verbunden werden, müssen die Leitungen gekreuzt werden. Werden die Steuerleitungen gleich im Stecker zurückgeführt (RTS auf CTS, DTR auf DSR), kommt man mit einer dreiadrigen Verbindung aus. So eine direkte Verbindung zwischen zwei Computern wird allgemein auch als "Nullmodem" bezeichnet, denn der Datenverkehr kann genauso ablaufen wie bei über Modem verbundenen Computern.

Bei der Pegelwandlung von TTL nach RS232 können wir praktischerweise auf einen bewährten integrierten Baustein zurückgreifen, den MAX232 von Maxim, um den sich inzwischen eine ganze Familie von Pegelwandlern mit verschiedenen Gehäusebauformen und unterschiedlichen elektrischen Eigenschaften gebildet hat. Der Baustein kann zwei Leitungen von TTL nach RS232 umsetzen und zwei weitere Leitungen von RS232 nach TTL. Somit kann ein Baustein die Sende- und Empfangsleitung (TXD, RXD) und zwei Handshakeleitungen (z. B. CTS, RTS) umsetzen, was in vielen Fällen ausreicht. Zusätzlich sind im Chip noch zwei Ladungspumpen integriert, welche die benötigten Spannungen von +12 V und -12 V erzeugen. Für diese Spannungswandler werden als externe Beschaltung lediglich vier Kondensatoren benötigt (1-uF-Tantal-Elkos). Im folgenden Bild (nach Maxim-Unterlagen) sind die Innenschaltung des Bausteins, die Beschaltung mit den Kondensatoren sowie das Pin-Layout zu sehen. Sie werden diesen Baustein in etlichen Anwendungen wieder antreffen.

Die RS422-Schnittstelle

Die RS422-Schnittstelle (und auch RS485) ist für die serielle Datenübertragung mit höherer Geschwindigkeit und über größere Entfernungen entwickelt worden und im industriellen Bereich weit verbreitet. Die seriellen Daten werden ohne Massebezug als Spannungsdifferenz zwischen zwei korrespondierenden Leitungen übertragen. Für jedes zu übertragende Signal existiert ein Adernpaar, das aus einer invertierten und einer nicht invertierten Signalleitung besteht.

Der Empfänger wertet die Differenz-Spannung zwischen beiden Leitungen aus, so dass Gleichtakt-Störungen auf der Übertragungsleitung nicht zu einer Verfälschung des Nutzsignals führen. Durch die Verwendung von abgeschirmten, paarweise verdrillten Cat-5-Kabeln lassen sich Distanzen von bis zu 1200 Metern (bei bis zu 100000 bps) realisieren. RS422-Sender stellen unter Last Ausgangspegel von +/-2 V zwischen den beiden Ausgängen zur Verfügung, die Empfängerbausteine erkennen Pegel von betragsmäßig 200 mV noch als gültiges Signal.

Bei RS422 gibt es also jeweils zwei Leiter für eine Übertragungsrichtung. Also muss das Kabel mindestens vier Adern haben, wobei es günstig ist, wenn jeweils zwei Adern paarweise verdrillt sind. An einen Sender können bis zu zehn Empfänger angeschlossen werden.

Die meisten externen Konverter von RS422 nach RS232 bieten aus Platzgründen keine weiteren Anschlüsse. In diesem Fall kann dann nur ein Software-Handshake genutzt werden. Stehen auf beiden Seiten Hardware-Handshakes (z. B. RTS/CTS) zur Verfügung, müssen noch jeweils vier weitere Verbindungen gezogen werden. Eine Terminierung des Kabels ist bei RS422-Verbindungen nur bei hohen Datenraten (mehr als 200 kbps) und großen Kabellängen erforderlich. In diesem Fall beugen relativ niederohmigeige Abschlusswiderstände (Größenordnung 120 Ohm) Leitungsreflexionen vor.

Die RS485-Schnittstelle

Die RS485-Schnittstelle stellt eine Erweiterung der RS422-Definition dar. Während RS422 lediglich den unidirektionalen Anschluss von bis zu zehn Empfängern an einen Sendebaustein zulässt, ist die RS485-Schnittstelle als bidirektionales Bussystem mit bis zu 32 Teilnehmern konzipiert. Physikalisch unterscheiden sich beide Schnittstellen nur unwesentlich. Die Leitungen dieser Industrie-Bus-Schnittstelle werden wie bei RS422 im Gegentakt betrieben; es werden jedoch minimal nur zwei Leitungen benötigt, die halbduplex angesteuert werden. Der Vorteil der Zweidraht-Technik liegt hauptsächlich in der Multimaster-Fähigkeit: Jeder Teilnehmer kann prinzipiell mit jedem anderen Teilnehmer Daten austauschen. Die Norm sieht eine maximale Kabellänge von 500 Metern vor. Dank moderner, symmetrischer Leitungstreiber und kapazitäts- bzw. dämpfungsarmer Twisted-pair-Kabeln kann die Entfernung zwischen zwei Endgeräten wesentlich erhöht werden: RS485 unterstützt Kabellängen von bis zu 1,2 km und Datenübertragungsraten bis zu 1 Mbps. Bei langen Übertragungsstrecken kann zwischen der Betriebserde des Daten-Senders und der des Empfängers eine hohe Potentialdifferenz auftreten. Daher ist eine galvanische Trennung der Schnittstelle vom Rest der Schaltung (z. B. durch schnelle Daten-Optokoppler) zwingend vorgeschrieben. Da mehrere Sender auf einer gemeinsamen Leitung arbeiten, muss durch ein Protokoll sichergestellt werden, dass zu jedem Zeitpunkt maximal ein Datensender aktiv ist. Alle anderen Sender müssen sich zu dieser Zeit in hochohmigenigem Zustand befinden. Die Aktivierung der Senderbausteine kann durch Schalten einer Handshake-Leitung oder automatisch (abhängig vom Datenfluss) erfolgen. Eine Terminierung des Kabels ist bei RS485-Verbindungen grundsätzlich nötig.

Beim RS485-2-Draht-Bus werden die Teilnehmer über eine maximal fünf Meter lange Stichleitung angeschlossen. Der Vorteil der Zweidraht-Technik liegt im Wesentlichen in der Multimaster-Fähigkeit, bei der jeder Teilnehmer prinziwiell mit jedem anderen Teilnehmer Daten austauschen kann. Der 2-Draht-Bus ist grundsätzlich nur halbduplexfähig, denn da nur ein Übertragungsweg zur Verfügung steht, kann immer nur ein Teilnehmer Daten senden. Erst nach Beendigung der Sendung können die Antworten anderer Teilnehmer erfolgen. Die wohl bekannteste auf der 2-Draht-Technik basierende Anwendung ist der Profibus.

Die beispielsweise vom DIN-Messbus genutzte 4-Draht-Technik unten) ist für Master/Slave-Anwendungen geeignet. Wie im Bild zu sehen ist, wird der Datenausgang des Masters auf die Dateneingänge aller Slaves verdrahtet. Die Datenausgänge der Slaves sind zusammen auf den Dateneingang des Masters geführt.

Für die Pegelanpassung zwischen TTL und RS485 können wir wieder ein IC von Maxim einsetzen, den MAX485. Dieser Chip hat neben der Spannungsversorgung lediglich je zwei Ein- und Ausgangspins sowie zwei Pins, mit denen die Ein- und Ausgänge aktiviert werden können (wir erinnern uns: es darf nur einen Master geben). Da der PC jedoch keine TTL-Pegel liefert, sondern eine RS232-Schnittstelle besitzt, brauchen wir einen RS232-zu-RS485-Umsetzer - und Sie ahnen es sicher schon: die beiden MAX-Bausteine werden miteinander verheiratet. Die Schaltung ist so auch recht einfach geworden.

Sie besteht nur aus den beiden Umsetzer-Bausteinen und einigen passiven Bauelementen. Die RS232-Signale werden vom MAX232 in TTL-Pegel umgewandelt. Diese gelangen dann an den MAX485, der die nötige Differenzspannung an den Ausgängen A und B erzeugt. Das DTR-Signal der RS232 steuert dabei die Richtung des Datenverkehrs. Als Ausgang wird auch auf der RS485-Seite ein 9-poliger Sub-D-Stecker vorgeschlagen, aber es kann auch jeder andere geeignete Steckverbinder zum Einsatz kommen. Befindet sich der Umsetzer an einem Ende der RS485-Busleitung, kann über eine Lötbrücke (oder einen Jumper) der Widerstand R2 aktiviert werden, womit auch gleich ein Leitungsabschluss vorhanden ist. Der Hersteller Maxim bietet eine ganze Reihe von Konverter-Bausteinen an (auch für die RS232-Schnittstelle), so z. B. MAX3440 - MAX3443 oder den MAX3535E, der intern eine galvanische Trennung realisiert und so den Computer von der RS485-Schnittstelle isoliert.

Die Stromschnittstelle(TTY)

Bei dieser Signalverbindung erfolgt die Datenübertragung nicht wie bei RS232 spannungsgesteuert, sondern durch einem eingeprägtem Linienstrom (typ. 20 bis 40 mA). Dadurch wirkt sich der Spannungsabfall auf der Datenleitung nicht wesentlich aus, so dass hier Kabellängen von bis zu einigen 100 m verwendet werden können. Diese Schnittstelle ist die Standardschnittstelle für Fernschreiber. Üblich ist eine Betriebsspannung von 12 bis 30 V, manchmal sogar bis zu 60 V. Im Ruhezustand bzw.\ während der Übertragung von 1-Bits fließt ein konstanter Strom, während 0-Bits durch eine Unterbrechung im Stromfluss gekennzeichnet sind.

Die Auskopplung der Nutzsignale aus der Stromschleife wird in der Regel über Optokoppler vorgenommen. Dies gewährleistet auch eine galvanische Trennung zwischen den verbundenen Geräten, so dass über TTY-Schnittstellen ohne weitere Schutzmaßnahmen eine isolierte Datenübertragung über eine große Distanz möglich ist. Der Vorteil der relativ sicheren Übertragung wird bei dieser Schnittstelle jedoch mit vergleichsweise niedrigen Datenraten erkauft (bis 19200 bps). Die maximale Entfernung wird mit 1 km bei 2400 bps angegeben.

Die Stromschnittstelle wird auch "20 mA"- bzw. "Current-Loop"-Schnittstelle genannt. Der Name "TTY-Schnittstelle" wurde ihr nach ihrem ersten Anwendungsgebiet verliehen, dem Fernschreibverkehr (Teletype). Heute wird die TTY-Schnittstelle nahezu ausschließlich für den Datenverkehr zur Programmierung und Ankopplung von SPS, elektronischen Waagen, industriellen Großanzeige-Displays und Protokolldruckern verwendet.

Ein weiteres Problem ist die Auslegung der Schnittstelle. Innerhalb jeder Stromschleife darf lediglich ein angeschlossenes Gerät den erforderlichen Schleifenstrom liefern. Sowohl die Sendekomponente als auch der Empfangsteil können aktiv (eingebaute Stromquelle) oder passiv (Schalter/Schalttransistor bzw. Optokoppler-Eingang) ausgelegt sein. Trifft ein passiver Sender auf einen passiven Empfänger, tut sich gar nichts. Ebenso können zwei aktive Komponenten nicht miteinander spielen.

Dagegen ist die Schaltung für eine Stromquelle recht einfach. Mit dem integrierten Spannungsregler LM317 lässt sich recht einfach eine Stromquelle aufbauen. Für den Strom gilt
            I = 1.25 / Rg
Für unseren Fall ergibt sich ein Wert von 20,1 mA bei einem Widerstandswert von 62 Ohm. Die Spannung der Stromschleife beträgt maximal Vs - 3 Volt. Die Spannung muss so gewählt werden, dass alle Spannungsabfälle in der Stromschleife aufgefangen werden. Bei einem Optokoppler kann man von maximal 2 V ausgehen, am LM317 fallen nochmals 3 V ab, so dass man mit einer 5-V-Spannungsquelle nur noch knapp hinkommt. Auf der sicheren Seite ist man mit einer Versorgungsspannung von 12 V, bei langen Leitungen braucht man eventuell noch mehr.

Programmierung mit C

Die nötigen Grundlagen für die Programmierung liefern die beiden HOWTO-Dokumente, das "Linux Serial HOWTO" von David Lawyer, das "Linux Serial Programming HOWTO" von Peter H. Baumann und das "Text-Terminal-HOWTO" von David Lawyer (Die HOWTOs werden bei den meisten Distributionen schon mit installiert oder lassen sich über das Dokumentationspakete nachinstallieren - sie sind aber auch im Internet oder auf der Webseite zum Buch zu finden). Da es sich bei den seriellen Schnittstellen um normale Gerätedateien handelt, gelten natürlich auch die entsprechenden Regeln der Programmierung für das Ansprechen von Geräten. Dafür können wir auf POSIX-standardisierte Funktionen zurückgreifen. Eine sehr gute Beschreibung finden Sie im GNU C-Library Reference Manual, welches den meisten Distributionen beiliegt (dort Kap. 12). Hier will ich Ihnen eine kurze Zusammenfassung des Terminal-IO bieten, was für die meisten Anwendungen ausreichen sollte.

Um mit einem User-Programm auf die serielle Schnittstelle zugreifen zu können, muss der entsprechende User auch die nötige Schreib- und Leseberechtigung für das Device haben (was normalerweise nicht der Fall ist). In der Regel reicht es, die User, die mit der seriellen Schnittstelle arbeiten, mit in die Gruppe uucp aufzunehmen (ggf. hilft ein Blick auf die Zugriffsrechte der Devices und in die Datei /etc/group). Sinnvoll ist auch das Anlegen eines eigenen (Pseudo-)Users für die Steuerungsprogramme.

Fast alle Veränderungen an den Übertragungsparametern von Terminals oder seriellen Schnittstellen erzielen Sie mit der Struktur termios (Terminal-IO-Settings). Diese Struktur besteht aus fünf Teilen, nämlich vier 32-Bit-Masken für die verschiedenen Flags, der line discipline und dem c_cc-Array, das weitere Parameter, z. B. Wartezeiten nach dem Senden bestimmter Zeichen und die Definition von Steuerzeichen, aufnimmt (adressiert wird es über Präprozessordefinitionen in /usr/include/termbits.h). Sie hat demnach folgendes Aussehen:

struct termios
  {
  /* Bitmaske fuer die Eingabe-Flags */       tcflag_t c_iflag;
  /* Bitmaske fuer die Ausgabe-Flags */       tcflag_t c_oflag;
  /* Bitmaske fuer die Control-Flags */       tcflag_t c_cflag;
  /* Bitmaske fuer lokale Einstellungen */    tcflag_t c_lflag;
  /* line discipline */                       char    __c_line;
  /* Array fuer Sonderzeichen/-funktionen */  cc_t c_cc[NCCS];
  }
Mit dem Shell-Kommando stty -a </dev/ttyS0 können Sie sich jederzeit alle Werte dieser Struktur anzeigen lassen. Mit diesem Kommando kann man auch zahlreiche Parameter setzen. Für den Zugriff auf die termios-Daten gibt es zwei Bibliotheksfunktionen:
int tcgetattr(int filedes, struct termios *termios_p)
/* liefert aktuelle Terminal-Settings */

int tcsetattr(int filedes, int when, const struct termios *termios_p) /* setzt Terminal-Settings */

Beiden Funktionen wird neben dem Zeiger auf eine Variable vom Typ termios auch der Dateideskriptor des Terminal-Devices übergeben. tcsetattr erwartet zusätzlich den Parameter when, mit dem sich festlegen lässt, wann die neuen Einstellungen übernommen werden sollen. Es gibt drei Möglichkeiten:

TCSANOW:        Einstellungen sofort ändern
TCSADRAIN:      Einstellungen ändern, nachdem alle eventuell noch gepufferten
                Daten gesendet wurden
TCSAFLUSH:      Einstellungen sofort ändern und Eingabepuffer löschen
TCSAFLUSH wäre also neben TCSANOW eine gute Wahl. Theoretisch ließe sich die Übertragungsgeschwindigkeit auch mit der termios-Struktur und tcsetattr() einstellen. Davon wird im GNU-Handbuch jedoch ohne Angabe von Gründen abgeraten. Zum Einstellen der Übertragungsgeschwindigkeit gibt es nämlich eine weitere Bibliotheksfunktion: int cfsetspeed(struct termios *termios_p, speed_t speed) Damit wird die Datenrate richtig in die Variable eingetragen. Für Hardware, die getrennte Einstellung von Sende- und Empfangsdatenrate erlaubt, gibt es übrigens noch die Funktionen cfsetospeed() und cfsetispeed(), die im Prinzip auch verwendet werden könnten (beim PC natürlich beide mit der gleichen Datenrate).

Alle genannten Funktionen liefern im Erfolgsfall den Wert 0 zurück und im Fehlerfall -1. Die Komplexität des Terminal-IO entsteht durch zahllose Flags, aus denen die vier Bitmasken zusammengesetzt sind. Die Flags und auch die termios-Struktur sind in der Datei /usr/include/termbits.h definiert und unter anderem im Mini-HOWTO dokumentiert.

Normalerweise werden nicht alle Flags benötigt, um die serielle Schnittstelle zum Laufen zu bringen. Deshalb will ich im Folgenden nur die wichtigsten von ihnen behandeln. Beginnen wir mit den Eingabeflags in c_iflag:

IGNBRK   Ignoriere Breaks
BRKINT   Beachte Breaks
IGNPAR   Ignoriere Parität
INLCR    Ersetze NL durch CR
IGNCR    Ignoriere CR
ICRNL    Ersetze CR durch NL
IUCLC    Großbuchstaben in Kleinbuchstaben umwandeln
IXON     XON/XOFF-Flusssteuerung einschalten
IXANY    Ausgabe fortsetzen mit einem beliebigen Zeichen
IXOFF    XON/XOFF-Flusssteuerung ausschalten
IMAXBEL  Klingle, wenn der Puffer voll ist (Zeilenende)
Die Ausgabeflags (c_oflag) sind noch wesentlich zahlreicher als die Eingabeflags, doch brauchen wir in der Regel nur wenige von ihnen. Meist reichen die folgenden:

ONLCR    Ersetze NL durch CR
OCRNL    Ersetze CR durch NL
ONOCR    Unterdrücken von CR in Spalte 0
ONLRET   Kein CR senden
OFILL    Füllzeichen NUL senden anstelle einer Pause
OFDEL    Füllzeichen ist DEL statt NUL
Die dritte Gruppe von Flags, c_cflag, ist für die Übertragungsgeschwindigkeit und das Datenformat zuständig. Zunächst die Flags für die Geschwindigkeit:

B0       hang up       B50      50 bps
B75      75 bps        B110     110 bps
B150     150 bps       B300     300 bps
B600     600 bps       B1200    1200 bps
B1800    1800 bps      B2400    2400 bps
B4800    4800 bps      B9600    9600 bps
B19200   19200 bps     B38400   38400 bps
B57600   57600 bps     B115200  115200 bps
Die anderen Flags dieser Gruppe steuern das Datenformat:

CS5      5 Bit
CS6      6 Bit
CS7      7 Bit
CS8      8 Bit
CSTOPB   2 Stoppbits statt einem
CREAD    Empfangsteil aktivieren
PARENB   Paritätsbit erzeugen
PARODD   ungerade Parität statt gerader
HUPCL    Verbindungsabbruch bei Ende des letzten Prozesses
CLOCAL   Terminal lokal angeschlossen (ignoriere CD)
CRTSCTS  Hardware-Handshake einschalten
CIGNORE  Ignoriere Controlflags
Aus der letzten Gruppe Flags (c_lflag) brauchen wir nur wenige:

ECHO  	 Einschalten der ECHO-Funktion
ICANON   Zeilenorientierter Eingabemodus (kanonischer Modus)
ISIG  	 Bestimmte Sonderzeichen lösen ein Signal aus (z. B.  Ctrl-C)
XCASE  	 Umwandeln von eingegebenen Groß- in Kleinbuchstaben
Grundsätzlich unterscheidet man beim Terminal-IO zwei Arten:

  c_cc[VMIN] > 0 c_cc[VMIN] = 0
c_cc[VTIME] > 0 1. Fall: read() liefert MIN Bytes, bevor die Zeit TIME abläuft oder read() liefert weniger als MIN Bytes, weil die Zeit TIME abgelaufen ist. Sind noch keine Daten empfangen worden, wartet read() auf min. ein Byte. Wenn das erste Byte gelesen wurde, läuft der Timer los, wobei jedes ankommende Byte den Timer wieder neu startet. Diese Methode ist günstig, wenn man große Datenmengen lesen, aber auch auf einzelne Zeichen reagieren muss. Aber es kann eine Blockierung stattfinden. 3. Fall: Diese Einstellung erlaubt es, das Lesen mit Timeout zu programmieren. Sobald ein Byte eintrifft, liefert read() dieses ab. Wenn die Zeit TIME seit dem Aufruf von read() verstrichen ist, liefert read() 0 (gelesene Bytes) zurück.
c_cc[VTIME] = 0 2. Fall: read() liefert mindestens MIN Bytes, sobald diese eingetroffen sind. Dieser Modus ist günstig, wenn möglichst viele Bytes mit einem read() gelesen werden sollen. Andererseits kann man auch auf ein einziges Byte reagieren (MIN = 1). Ist MIN größer als die Anzahl der bei read() angegebenen Zeichen, wird gewartet, bis MIN Bytes gelesen, aber nur n Bytes an read() geliefert wurden; ein zweites read() liefert dann den Rest. Auch hier kommt es zur Blockierung, wenn nicht genügend Bytes eintreffen. 4. Fall: read liefert die Anzahl Bytes, die anliegen. Sind keine Daten vorhanden, wird sofort 0 (gelesene Bytes) zurückgegeben. Der Treiber wartet also niemals auf Daten, sondern kehrt immer sofort zurück.

Da es sich bei den seriellen Schnittstellen nicht um normale Dateien handelt, können beim open()-Aufruf gegebenenfalls dateiuntypische Fehler auftreten. So kann zum Beispiel der Treiber den Fehlercode EBUSY zurückmelden, wenn gerade ein anderer Prozess das Device benutzt. Oder er hält das Programm so lange an ("blocking open"), bis die Carrier-Leitung des Modems aktiv wird (was bei direkt angeschlossenen Geräten durch das Fehlen dieser Leitung scheinbar auftritt). Es gibt jedoch einen Mechanismus, um das Blockieren zu umgehen: beim open()-Aufruf muss das Flag O_NDELAY mitgegeben werden. Das sieht folgendermaßen aus:

file_descr = open("/dev/ttyS0", O_RDWR | O_NDELAY | O_NOCTTY);
/*                    Modus:    read      nicht     nicht              */
/*                              write     warten    controlling entity */
Sobald die Gerätedatei erfolgreich geöffnet ist, stellen Sie O_NDELAY sofort wieder ab, da sonst zukünftige read()-Kommandos nicht auf Daten warten, sondern immer sofort zurückkommen und damit ein lastintensives "busy waiting" durchführen (fcntl( filedescriptor, F_SETFL, O_RDWR );). Eine Funktion zum Öffnen eines seriellen Ports könnte also folgendermaßen aussehen:
int open_port(int port)
  {
  /*
   * Oeffnet seriellen Port
   * Gibt das Filehandle zurueck oder -1 bei Fehler
   * der Parameter port muss 0, 1, 2 oder 3 sein
   *
   * RS232-Parameter
   * - 19200 baud
   * - 8 bits/byte
   * - no parity
   * - no handshake
   * - 1 stop bit
   */
   int fd;
   struct termios options;
   switch (port)
     {
     case 0: fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); break;
     case 1: fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY); break;
     case 2: fd = open("/dev/ttyS2", O_RDWR | O_NOCTTY | O_NDELAY); break;
     case 3: fd = open("/dev/ttyS3", O_RDWR | O_NOCTTY | O_NDELAY); break;
     default: fd = -1;
     }
   if (fd >= 0)
     {
     /* get the current options */
     fcntl(fd, F_SETFL, 0);
     if (tcgetattr(fd, &options) != 0) return(-1);
     bzero(&options, sizeof(options)); /* Structure loeschen, ggf vorher sichern
                                          und bei Programmende wieder restaurieren */

     cfsetspeed(&options, B19200);       /* setze 19200 bps */
     /* Alternativ:                                         */
      * cfsetispeed(&options, B19200);                      *
      * cfsetospeed(&options, B19200);                      */

     /* setze Optionen */
     options.c_cflag &=  PARENB;         /* kein Paritybit */
     options.c_cflag &=  CSTOPB;         /* 1 Stoppbit */
     options.c_cflag &=  CSIZE;          /* 8 Datenbits */
     options.c_cflag |= CS8;
     options.c_cflag |= (CLOCAL | CREAD);/* CD-Signal ignorieren */
     /* Kein Echo, keine Steuerzeichen, keine Interrupts */
     options.c_lflag &=  (ICANON | ECHO | ECHOE | ISIG);
     options.c_oflag &=  OPOST;          /* setze "raw" Input */
     options.c_cc[VMIN]  = 0;            /* warten auf min. 0 Zeichen */
     options.c_cc[VTIME] = 10;           /* Timeout 1 Sekunde */
     tcflush(fd,TCIOFLUSH);
     if (tcsetattr(fd, TCSAFLUSH, &options) != 0) return(-1);

     fcntl(fd, F_SETFL, FNDELAY);
     fcntl(fd, F_SETFL, 0);
     }
  return(fd);
  }
Nach dem Öffnen können Sie nach Herzenslust mit read() und write() seriell Daten empfangen und senden. Jedoch kann - wie schon angedeutet - immer mal etwas Ungewöhnliches passieren. Was also tun, wenn irgend etwas schiefgeht und die Zeichenkette, auf die das Programm wartet, nie kommt? Das Programm würde warten, bis der Anwender es manuell abbricht. Die Lösung ist recht einfach: mit den Bibliotheksfunktionen alarm() und signal() installiert man einen "Alarmtimer", der bei Bedarf einen Timeout erzeugt und der Routine sagt, dass die Wartezeit vorbei ist:
void alarm_handler(void)
  { timeout = 1; }

signal(SIGALRM, alarm_handler);
alarm(60); /* Wartezeit setzen */

...

if (read(file_descr,buffer,1) != 1 && errno == EINTR && timeout)
  {
  fprintf(stderr,"TIMEOUT!\n"); break;
  }
...

alarm(0);  /* Alarm abschalten */
Mit diesen Informationen lassen sich nicht-interaktive Programme für die serielle Schnittstelle schreiben. Was noch fehlt, ist die Möglichkeit, mehrere Schnittstellen gleichzeitig zu überwachen, beispielsweise gleichzeitig Schnittstelle und Tastatur. Je nach Programm würde read() so lange warten, bis etwas an der Schnittstelle eintrifft, auch wenn Sie in der Zwischenzeit wie wild auf die Tastatur hämmern. Eine Möglichkeit wäre, über O_NDELAY zu arbeiten oder VMIN auf 0 zu setzen. Das ist jedoch beides nicht effizient, weil das Programm "busy waiting" mit voller CPU-Leistung angestrengt auf Daten wartet. Glücklicherweise gibt es einen Mechanismus, mit dem man warten kann, bis auf einem File-Deskriptor etwas zum Lesen bereitliegt, nämlich select(). Der Aufrufer übergibt der select()-Funktion eine Liste mit File-Deskriptoren, von denen gelesen oder auf die geschrieben werden soll. Sobald es möglich ist (Daten eingetroffen oder Ausgabewarteschlange frei), kehrt select() mit der entsprechenden Information zurück. Zusätzlich kann man einen Timeout definieren, der angibt, nach welcher Zeit die Funktion in jedem Fall aufgeben soll. Das folgende Beispiel ist dem Serial-Programming-HOWTO entnommen:

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
main()
  {
  int fd1, fd2; /* input sources 1 and 2 */
  fd_set readfs; /* file descriptor set */
  int maxfd; /* maximum file desciptor used */
  int loop=1; /* loop while TRUE */

  /* open_input_source opens a device, sets the port correctly, and
     returns a file descriptor */
  fd1 = open_input_source("/dev/ttyS0");
  if (fd1<0) exit(0);
  fd2 = open_input_source("/dev/ttyS1");
  if (fd2<0) exit(0);
  maxfd = MAX (fd1, fd2)+1; /* maximum bit entry (fd) to test */

  /* loop for input */
  while (loop)
    {
    FD_SET(fd1, &readfs); /* set testing for source 1 */
    FD_SET(fd2, &readfs); /* set testing for source 2 */
    /* block until input becomes available */
    select(maxfd, &readfs, NULL, NULL, NULL);

    if (FD_ISSET(fd1)) /* input from source 1 available */
    handle_input_from_source1();
    if (FD_ISSET(fd2)) /* input from source 2 available */
    handle_input_from_source2();
    }
  }
Statt der zweiten seriellen Schnittstelle könnten Sie auch mittels FD_SET(STDIN, &readfs); die Tastatur in select() einklinken und dann wahlweise Daten der Tastatur und der RS232 bearbeiten. Das Beispiel blockiert, bis Daten eintreffen. Will man nach einer bestimmten Zeit abbrechen, muss nur der select()-Aufruf geändert werden:

int res;
struct timeval Timeout;

/* set timeout value within input loop */
Timeout.tv_usec = 0; /* milliseconds */
Timeout.tv_sec = 1; /* seconds */
res = select(maxfd, &readfs, NULL, NULL, &Timeout);

if (res == 0)
  /* number of file descriptors with input = 0, timeout occurred. */
Bei diesem Beispiel gibt es nach einer Sekunde einen Timeout. In diesem Fall liefert select() 0 zurück.

In manchen Fällen wollen Anwendungen einzelne Kontrollleitungen gezielt auf bestimmte Pegel setzen oder einzelne Pegel abfragen. Man kann so z. B. Tasten einlesen, ein Relais schalten oder die Signale eines Funkuhr-Empfängers detektieren (da reicht dann oft ein Transistor als Pegelwandler, und man kann auch noch auf den MAX232 verzichten). Zum Abfragen der Statusleitungen gibt es den ioctl()-Aufruf mit den Parametern TIOCMGET (Status lesen) und TIOCMSET (Status schreiben). ioctl() liefert einen Integerwert zurück, in dem für jede Leitung ein dem Leitungspegel entsprechendes Bit gesetzt oder gelöscht ist. Die Bits sind über symbolische Konstanten (TIOCM_DTR, TIOCM_RTS usw.) ansprechbar. Ein Beispielprogramm, das alle Leitungen auf einem gegebenen Port überwacht und anzeigt, findet sich im folgenden Listing. Setzen kann ein Programm die Leitungen alle gemeinsam über den Aufruf ioctl(fd,TIOCMSET,&status). Die Bits in der Variablen status haben dieselbe Bedeutung wie bei TIOCMGET.

Die serielle Schnittstelle besitzt neben den Datenleitungen ja noch Steuerleitungen, die sich als Ein- und Ausgabe nutzen lassen:

Pin Bitmaske Konstante I/O
DTR 0002 TIOCM_DTR ->
RTS 0004 TIOCM_RTS ->
CTS 0020 TIOCM_CTS <-
CD 0040 TIOCM_CAR <-
RI 0080 TIOCM_RNG <-
DSR 0100 TIOCM_DSR <-

Das Listing enthält Funktionen zum Setzen und Rücksetzen von RTS und DTR, Funktionen für das Abfragen der Einzelwerte von CTS, CD, RI und DSR sowie eine Funktion zum Abfragen des Gesamtstatus (mit allen sechs Werten). Das Hauptprogramm demonstriert deren Anwendung.

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

int fd = 0;

void SetRTS(int fd)
/* Setzt RTS auf ON */
  {
  int currstat;
  ioctl(fd, TIOCMGET, &currstat);
  currstat |= TIOCM_RTS;
  ioctl(fd, TIOCMSET, &currstat);
  }

void ResetRTS(int fd)
/* Setzt RTS auf OFF */
  {
  int currstat;
  ioctl(fd, TIOCMGET, &currstat);
  currstat &= ~TIOCM_RTS;
  ioctl(fd, TIOCMSET, &currstat);
  }

void SetDTR(int fd)
/* Setzt DTR auf ON */
  {
  int currstat;
  ioctl(fd, TIOCMGET, &currstat);
  currstat |= TIOCM_DTR;
  ioctl(fd, TIOCMSET, &currstat);
  }

void ResetDTR(int fd)
/* Setzt DTR auf OFF */
  {
  int currstat;
  ioctl(fd, TIOCMGET, &currstat);
  currstat &= ~TIOCM_DTR;
  ioctl(fd, TIOCMSET, &currstat);
  }

void ClearSerPins(int fd)
/* Setzt alle Pins der Schnittstelle auf OFF */
  {
  int currstat = 0;
  ioctl(fd, TIOCMSET, &currstat);
  }

int GetRI(int fd)
/* Gibt 1 zurueck, wenn RI gesetzt ist, sonst 0 */
  {
  int  currstat;
  ioctl(fd, TIOCMGET, &currstat);
  return((currstat & TIOCM_RNG)?1:0);
  }

int GetCD(int fd)
/* Gibt 1 zurueck, wenn CD gesetzt ist, sonst 0 */
  {
  int  currstat;
  ioctl(fd, TIOCMGET, &currstat);
  return((currstat & TIOCM_CAR)?1:0);
  }

int GetDSR(int fd)
/* Gibt 1 zurueck, wenn DSR gesetzt ist, sonst 0 */
  {
  int  currstat;
  ioctl(fd, TIOCMGET, &currstat);
  return((currstat & TIOCM_DSR)?1:0);
  }

int GetCTS(int fd)
/* Gibt 1 zurueck, wenn CTS gesetzt ist, sonst 0 */
  {
  int  currstat;
  ioctl(fd, TIOCMGET, &currstat);
  return((currstat & TIOCM_CTS)?1:0);
  }

int GetSerStat(int fd)
/* Gibt den kompletten seriellen Status zurueck *
 * ggf. RTS und DTR ausblenden:                 *
 * currstat &=  (TIOCM_DTR | TOICM_RTS)         */
  {
  int  currstat;
  ioctl(fd, TIOCMGET, &currstat);
  return(currstat);
  }

int main(int argc, char** argv)
  {
  int i, stat;

  /* Parameterprüfung: Das zu verwendende Device muss  */
  /* als erster Parameter angegeben werden.            */
  if(argc != 2)
    {
    printf("Fehler: Ungültige Parameter-Anzahl.\n");
    printf("Aufruf: serpin <device>\n");
    return 1;
    }

  /* Das Device öffnen */
  if((fd = open(argv[1], O_RDWR | O_NDELAY)) < 0)
    {
      printf("Fehler: Device \"%s\" kann nicht geöffnet werden.\n",
      argv[1]);
      return 2;
    }

  /* alles loeschen */
  ClearSerPins(fd);

  /* mit RTS und DTR blinken */
  for(i=0; i<5; i++)
    {
    usleep(300000);
    SetRTS(fd);
    usleep(300000);
    ResetRTS(fd);
    usleep(300000);
    SetDTR(fd);
    usleep(300000);
    ResetDTR(fd);
    }
/* Status abfragen, bis RI gesetzt wird */
  while(!GetRI(fd))
    {
    stat = GetSerStat(fd);
    printf("%6X  ",stat);
    printf("+%d+ ",GetCTS(fd));
    if (stat & TIOCM_RNG) puts("RI ");
    if (stat & TIOCM_CAR) puts("CD ");
    if (stat & TIOCM_DSR) puts("DSR ");
    if (stat & TIOCM_CTS) puts("CTS ");
    printf("\n");
    sleep(1);
    }
  close(fd);
  return 0;
  }
Quellcode serpin.c

Prinzipiell könnten Sie auch direkt auf den Modem-Port der Schnittstelle schreiben (mittels inb() und outb(), was aber nicht nur wegen der Portabilität unschön ist.

An die Ausgänge lassen sich LEDs direkt anschließen; wenn man Dual-LEDs nimmt, kann man sogar 0 und 1 aktiv anzeigen (z. B. rot-grün - was einen Farbenblinden dann in den Wahnsinn treibt). Einen LED-Strom von 20 mA verkraftet die Schnittstelle normalerweise wunderbar, nur einige Laptops sind manchmal schwach auf der Brust. Der Vorwiderstand wäre dann minimal (12-2)/0.02 = 1000 Ohm. Bei Low-Power-LEDs reicht meist der doppelte Wert.

Schaltet man einen Ausgang auf 1, kann er +12 V für die Eingänge liefern. Die Schnittstelle am PC liefert den Eingangswert 0, wenn der Eingang offen ist. Man kann also vier Taster oder Schalter einseitig mit dem RTS-Ausgang verbinden und die andere Seite dieser Taster/Schalter wieder mit den Eingangspins - und braucht also keinerlei aktive Komponenten.

Diese Tasten bzw. Schalter kann man nun abfragen und im Programm darauf reagieren. Bei der Tastenabfrage muss man die Entprellung per Software durchführen, etwa in der Form:

if (GetRI(fd))          /* Taste RI gedrückt */
  {
  usleep(100*1000);     /* 0,1 s Pause */
  if (GetRI(fd))        /* Wenn immer noch gedrückt... */
    {
    while(GetRI(fd));   /* ... warten auf's Loslassen */
    do_something_awful();
    }
  }
Bei Servern, die ohne Tastatur und Bildschirm laufen, kann man mit so einer Tastengruppe einige Funktionen steuern (Herunterfahren, NFS-Mount und -Unmount etc.). Per LED an DTR/RTS ist eine Rückmeldung möglich. Mit diesem kleinen Einstieg schließt dieser Abschnitt.

Weiterführende Links

Zum vorhergehenden Abschnitt Zum Inhaltsverzeichnis Zum nächsten Abschnitt

Copyright © FH München, FB 04, Prof. Jürgen Plate
Letzte Aktualisierung: 07. Apr 2010