 |
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:
- Hardware-Handshake: Der Empfänger steuert über entsprechende Leitungen die Handshake-Eingänge
des Senders (CTS, DSR) mit seinen Handshake-Ausgängen (DTR, RTS).
- Software-Handshake: Der Empfänger sendet zur Steuerung des Datenflusses spezielle
Zeichen an den Sender (z. B. XON, XOFF).
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.
| Port | Funktion | Bem. |
| 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:
- Empfangspuffer einrichten
- Baudrate einstellen
- Interruptvektor (z. B. IRQ4) auf die eigene Interrupt-Routine zeigen lassen
- Dummy-Read auf das Datenregister ausführen, um ein eventuell
gesetztes Interrupt-Bit für "Daten-Empfangen" zu löschen.
- Am Interrupt-Controller 8259 den Interrupt (z. B. IRQ4) freigeben.
- 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:
- Empfangenes Byte vom 8250 abholen (Datenregister) und im Pufferbereich speichern.
- "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:
- GND bildet das gemeinsame Massepotential für die Datenleitungen.
- TXD führt die Sendedaten des Computers zum Peripheriegerät.
- RXD liefert die Daten vom Peripheriegerät zum Rechner.
- RTS zeigt der Peripherie die Übertragungsbereitschaft des Computers an.
- CTS signalisiert die Bereitschaft der Peripherie, Daten zu empfangen.
- DTR steuert bei Modems das Anschalten an die Telefonleitung.
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.
| ITU | DIN | US | 25-pol. | 9-pol. | Beschreibung | Richt. |
| 101 | E 1 | - | 1 | - | Schutzerde | - |
| 102 | E 2 | GND | 7 | 5 | Signalerde (Ground) | - |
| 103 | D 1 | TXD | 2 | 3 | Sendedaten (Transmit Data) | |
| 104 | D 2 | RXD | 3 | 2 | Empfangsdaten (Receive Data) | |
| 105 | S 2 | RTS | 4 | 7 | Sendeteil einschalten (Request To Send) | |
| 106 | M 2 | CTS | 5 | 8 | Sendebereitschaft (Clear To Send) | |
| 107 | M 1 | DSR | 6 | 6 | Betriebsbereitschaft (Data Set Ready) | |
| 108.2 | S 1.2 | DTR | 20 | 4 | Terminal ist bereit (Data Terminal Ready) | |
| 109 | M 5 | DCD | 8 | 1 | Empfangspegel (Data Carrier Detect) | |
| 125 | M 3 | RI | 22 | 9 | Ankommender 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:
- kanonischer Modus: Hier erfolgt das Lesen und Schreiben auf
das Device zeilenorientiert. Eine Eingabe wird erst weitergereicht,
wenn ein Zeilenabschluss (Linefeed, NL) oder Carriage Return (CR)) übertragen
wurde. Für diesen Mode benötigt man die Steuerzeichen des c_cc-Arrays.
Ein Programm wartet beim Lesen in diesem Modus so lange, bis tatsächlich
eine komplette Zeile empfangen wurde. Wird kein Zeilenabschluss gelesen,
so wird für immer und ewig gewartet. Die Aufgabe des Zwischenspeicherns
übernimmt der Kernel.
- nichtkanonischer Modus: Hier wird nicht zeilenweise gelesen,
sondern entweder auf eine bestimmte Anzahl von Bytes gewartet oder nach
einer gewissen Zeit die bis dahin eingetroffenen Bytes abgeliefert. Hierfür
müssen zwei Felder des Arrays c_cc gesetzt werden. In c_cc[VTIME]
wird die Wartezeit in Zehntelsekunden und in c_cc[VMIN] das Minimum
der zu lesenden Bytes angegeben. Die folgenden vier Konstellationen sind
denkbar (siehe Tabelle ). Wenn das Programm nicht ewig
auf eine Eingabe warten soll, nimmt man also am besten den dritten Fall.
| | 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
Copyright © FH München, FB 04, Prof. Jürgen Plate
Letzte Aktualisierung: 07. Apr 2010