![]() |
Raspberry Pi: Serielle SchnittstelleProf. Jürgen Plate |

Ein Byte besteht dann aus einer Folge von acht Datenbits, die von Start- und Stoppbit eingerahmt werden. Zwischen zwei aufeinanderfolgenden Bytes können sich auch beliebig lange Pausen befinden, da der Beginn eines Zeichens am Startbit eindeutig erkannt wird. Daher nennt man diese Form der Übertragung "asynchron". Durch die asynchrone Übertragung wird die Übertragungsrate gesenkt, da für z. B. 8 Informationsbits 10 Bits über die Leitung gesendet werden. Nach dem Stoppbit kann sofort wieder eine neue Übertragung mit einem Startbit beginnen.
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 115200Da 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 Schnittstelle des Raspberry Pi ist über GPIO14 (TXD) und GPIO15 (RXD) erreichbar. Günstigerweise liegen die entspechenden Pins auf der Steckerleiste P1 untereinander (6 - GND, 8 - TXD, 10 - RXD), sodass Sie auch mit einer dreipoligen Pfostenbuchse abgegriffen werden können (derartige Buchsen findet man auch of in PC-Bastelkiste). Aber immer daran denken: Auch diese Schnittstelle arbeitet mit nur 3,3 Volt.
Beim Raspberry Pi sind keine speziellen Leitungen für den Hardware-Handshake vorgesehen. Falls ein hardwarebasierter Handshake benötigt wird, muss man auf freie GPIO-Pins zurückgreifen.
Per Voreinstellung ist auf der Schnittstelle, die unter Linux auf /dev/ttyAMA0 angesprochen wird, eine serielle Login-Konsole konfiguriert, auf der auch der gesamte Bootvorgang protokolliert wird. Deshalb kann man diese Schnittstelle nicht so ohne Weiteres für andere Zwecke verwenden. Das bedeutet, dass Sie zuerst die serielle Konsole abschalten müssen. Maßgeblich dafür sind zwei Dateien:
Die Datei /boot/cmdline.txt regelt den Bootvorgang des Pi. Dort werden diverse Boot-Optionen eingestellt, so auch die serielle Konsole. Per Default steht relativ wenig in der Datei, wobei es bei kommenden Versionen schon wieder anders aussehen kann (für die Darstellung hier wurde die Zeile umbrochen!):
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwaitAus diesen Optionen müssen Sie nun die Angaben zur Konsole "ttyAMA0" löschen, aber alles andere unbedingt unverändert lassen. Am Besten machen Sie zuerst ein Backup der Datei (cp /boot/cmdline.txt /boot/cmdline.bak). Dann ändern Sie in der Originaldatei die Zeile:
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwaitin die folgende Zeile (oben sind die zu löschenden Teile farbig hervorgehoben):
dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
Nun wird die Datei /etc/inittab bearbeitet. In ihr ist die serielle Schnittstelle als Login-Schnittstelle definiert. Dazu Öffnen Sie die Datei und bearbeiten den folgenden Eintrag:
#Spawn a getty on Raspberry Pi serial line T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100Ändern Sie die Zeile durch ein davor gestelltes Kommentarzeichen in
#Spawn a getty on Raspberry Pi serial line # T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100Damit ist die Login-Funktion abgeschaltet. Wenn Sie den Pi nun neu starten, können Sie die serielle Schnittstelle beliebig nutzen.
Damit der User pi auch auf die Schnittstelle zugreifen darf, muss er zusätzlich in die Gruppe "dialout" aufgenommen werden. Das erreichet man mit dem Kommando
sudo usermod -a -G dialout piBei neueren Versionen des Raspian-Betriebssystems kann die Aktivierung/Deaktivierung der Schnittstelle auch über raspi-config erfolgen.
Die neue Bluetooth-4.2-Schnittstelle des Modells 3 wird leider über den PL011-UART angebunden. Dieser UART war bei den Vorgängermodellen für die serielle Schnittstelle auf den GPIO-Pins 8 und 10 zuständig. Nun ist der Mini-UART an diesen GPIO-Pins verfügbar. Der Mini-UART ist eine kleine Variante des ursprünglichen UART, die neben einem geringen Durchsatz auch nicht mehr so stabil ist wie zuvor. Die Baud-Rate des Mini-UART wird vom System-Takt (Videocore IV) abgeleitet. Das bedeutet, dass die Baud-Rate nicht mehr stabil bleibt, sondern je nach CPU-Auslastung stark schwankt. Deshalb schlagen die Entwickler vor, den Core-Takt fest auf 250 MHz einzustellen. Alternativ kann, falls nicht benötigt, Bluetooth mittels Devicetree-Overlay abgeschaltet werden. Dazu fügen Sie am Ende der Datei /boot/config.txt die Zeile
dtoverlay=pi3-miniuart-bthinzu. Nach einem Neustart funktioniert der UART wieder wie in den Vorgängerversionen.
Will man Bluetooth behalten, hilft ein USB-seriell-Adapter. Dieses sieht meist aus wie ein Kabel mit einem USB-A-Stecker an einem Ende und einem 9-poligen DB9-Stecker auf der anderen Seite. In den seriellen Anschluss ist ein Hardware-Chipsatz eingebaut, der die notwendige USB-seriell-Umwandlung durchführt. Stecken Sie den Adaper am RasPi ein und wartet Sie einige Sekunden. Dann starten Sie in der Konsole das Kommando dmesg. Am Ende des Outputs sollte nun das Protokoll zum Adapter erscheinen, z. B.:
... [ 2329.049523] usb 1-1.4: new full-speed USB device number 7 using dwc_otg [ 2329.178464] usb 1-1.4: New USB device found, idVendor=0403, idProduct=6001 [ 2329.178489] usb 1-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ 2329.178502] usb 1-1.4: Product: USB Serial Converter [ 2329.178514] usb 1-1.4: Manufacturer: FTDI [ 2329.178526] usb 1-1.4: SerialNumber: FT9GC7Y9 [ 2329.189325] ftdi_sio 1-1.4:1.0: FTDI USB Serial Device converter detected [ 2329.189470] usb 1-1.4: Detected FT232RL [ 2329.190563] usb 1-1.4: FTDI USB Serial Device converter now attached to ttyUSB0Bei diesem Beispiel handet es sich im Adapter um den bekannten Chip von Future Technology Devices International (FTDI). Wichtig für später sind die Hersteller- und Geräte-Id (idVendor=0403, idProduct=6001). Diese können Sie auch später noch mit dem Kommando lsusb ermitteln, z. B.:
... Bus 001 Device 008: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub ...Wenn alles gut gegangen ist, ist Ihr Adapterkabel jetzt installiert und einsatzbereit, aber welches ist unter Linux der zugehörige serielle Port? Wie bei allen Geräten unter Linux, befindet sich die Gerätedatei unterhalb von /dev/. Da es sich um einen USB-basierten seriellen Port handelt, heißt er in der Regel /dev/ttyUSB0. Er kann aber, je nach Treiber, auch andere Namen haben, z. B. /dev/ttyACM0.
Um den Adapter auf einen festen Namen "festzunageln", kann man eine udev-Regel verwenden. Dazu erstellen Sie an besten eine neue Datei im Verzeichnis /etc/udev/rules.de, die Sie beispielsweise seriell.rules nennen (wichtig ist nur die Endung ".rules"). Dort tragen Sie für den Adapter eine Zeile nach dem Muster
SUBSYSTEM=="tty", ATTRS{idVendor}=="vvvv", ATTRS{idProduct}=="pppp", SYMLINK+="geraete_name"
ein. Für "vvvv" wird die Vendor-Id und für "pppp" die Product-Id eingesetzt. Als
Gerätenamen wählen Sie irgend etwas Aussagekräftiges, z. B. "Seriell" oder auch
"COM1". Sie können dann immer über das symbolische Link /dev/Seriell
auf die Schnittstelle zugreifen. Hier noch einige Beispiele für gängige Produkte:
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="FTDI"
SUBSYSTEM=="tty", ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0031", SYMLINK+="ONDA"
SUBSYSTEM=="tty", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="14ac", SYMLINK+="HUAWEI"
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="PROLIFIC"
Nach einem Reboot steht das Symlink dann zur Verfügung. Um im laufenden Betrieb, etwa
nach dem Erstellen der Rules-Datei, zu testen, ob es klappt, können Sie die Datei
mittels
sudo udevadm triggereinbinden.
Wenn Sie wollen, dass alle User auf die Schnittstelle zugreifen können (nicht nur diejenigen, die zur Gruppe dialout gehören), hängen Sie noch ', MODE="0666"' an die Regel an.
Die Programmierung der seriellen Schnittstelle funktioniert im Prinzip wie bei jedem UNIX- oder Linux-Rechner und ist kein Hexenwerk. Natürlich sollte auf der Gegenseite auch ein System sitzen, dessen serielle Schnittstelle funktioniert - und die auf die gleiche Datenrate eingestllt ist. Übrigens kann analog der "echten" seriellen Schnittstelle ein USB-Seriell-Adapter an der USB-Schnittstelle angesprochen werden - oder ein anderes USB-Gerät, was sich auf ein Pseudo-Seriell-Device stützt. Denken Sie auch daran, dass so eine Schnittstelle sich nicht unbedingt so "brav" wie eine Festplatte verhält. Unter Umständen antwortet das angeschlossene Gerät nicht wie erwartet oder gar nicht. Manche Mess-Systeme spucken auch ständig Daten auf die Leitung oder benötigen die Ansteuerung bestimmter Handshake-Leitungen. Auch die Buchsenbeschaltung selbst kann nicht so sein, wie Sie das erwarten.
Zum Testen Ihres Programms können Sie die Schnittstelle mit sich selbst "reden" lassen, indem Sie TXD und RXD über einen Widerstand von ca. 1,5 - 2 Kiloohm verbinden. Der Widerstand ist im Prinzip nicht nötig, schützt aber die Leitungen des GPIO, falls sie versehentlich mal beide als Ausgang programmiert wurden (z. B. durch eine in Vergessenheit geratene Init-Routine).
Für alle Programme werden einige Standard-Include-Dateien benötigt, die Sie am Besten immer gleich in Ihre Programme einbinden:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <termios.h>
Für die Angabe der Baudrate gibt es in den Include-Dateien definierte Konstante, die Sie auch verwenden sollten. Die langsameren Übertragungsgeschwindigkeiten orientieren sich an den inzwischen schon historischen Fernschreibern, wobei die ursprüngliche Rate von 150 Bit/s (bps) immer wieder verdoppelt wurde:
B1200, B2400, B4800, B9600, B19200, B38400Beim PC kamen dann noch hinzu (Modem bzw. maximale Geschwindigkeit der seriellen PC-Schnittstelle):
B57600, B115200Mit USB ginge es natürlich noch schneller, so dass der Raspberry Pi auch noch die folgenden Angaben kennt:
B230400, B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000Es gibt aber nur sehr selten Anlass, Datenraten über 115200 bps zu verwenden. Serielle Geräte sind meist relativ langsam und liefern auch nicht allzuviele Daten. Zudem ist es auch schon für die Software höchst anspruchsvoll, die ankommenden (oder abgehenden) Daten mit hoher Geschwindigkeit zu verarbeiten. Ganz abgesehen davon, dass "normale" serielle Kabel etc. gar nicht für solche Raten spezifiziert sind. Daher mein Tipp: Bleiben Sie bei 9600 bps oder 19200 bps, das reicht in fast allen Fällen aus und Sie sind auf der sicheren Seite.
Ein wichtiger Punkt betrifft die Zugriffsrechte. Normalerweise ist der User "pi" in die Gruppe der seriellen Schnittstelle (anfangs "dialout", jetzt "tty") eingetragen und kann somit darauf zugreifen. Arbeiten Sie mit einer anderen Userkennung, muss ggf. der User in die entsprechende Gruppe eingetragen werden:
sudo usermod -a -G dialout <Username>
bzw.
sudo usermod -a -G tty <Username>
Oder sie ändern gleich die Zugriffsrechte für die Schnittstelle:
sudo chmod a+rw /dev/ttyAMA0Die Änderungen der Gruppe werden erst nach dem nächsten Login des Users wirksam.
Die folgenden Links beschreiben die serielle Programmierung recht umfangreich, weshalb im Anschluss nur einige Tipps und Handreichungen gegeben werden.
options.c_cflags &= ~PARENB options.c_cflags &= ~CSTOPB options.c_cflags &= ~CSIZE; options.c_cflags |= CS8;
options.c_cflags |= PARENB options.c_cflags &= ~PARODD options.c_cflags &= ~CSTOPB options.c_cflags &= ~CSIZE; options.c_cflags |= CS7;
options.c_cflags |= PARENB options.c_cflags |= PARODD options.c_cflags &= ~CSTOPB options.c_cflags &= ~CSIZE; options.c_cflags |= CS7;
int open_serial(void)
{
/*
* Oeffnet seriellen Port
* Gibt das Filehandle zurueck oder -1 bei Fehler
*
* RS232-Parameter:
* 19200 bps, 8 Datenbits, 1 Stoppbit, no parity, no handshake
*/
int fd; /* Filedeskriptor */
struct termios options; /* Schnittstellenoptionen */
/* Port oeffnen - read/write, kein "controlling tty", Status von DCD ignorieren */
fd = open("/dev/ttyAMA0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd >= 0)
{
/* get the current options */
fcntl(fd, F_SETFL, 0);
if (tcgetattr(fd, &options) != 0) return(-1);
memset(&options, 0, sizeof(options)); /* Structur loeschen, ggf. vorher sichern
und bei Programmende wieder restaurieren */
/* Baudrate setzen */
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;
/* 19200 bps, 8 Datenbits, CD-Signal ignorieren, Lesen erlauben */
options.c_cflag |= (CLOCAL | CREAD);
/* Kein Echo, keine Steuerzeichen, keine Interrupts */
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag = IGNPAR; /* Parity-Fehler ignorieren */
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); /* Puffer leeren */
if (tcsetattr(fd, TCSAFLUSH, &options) != 0) return(-1);
}
return(fd);
}
Besonders wichtig sind die beiden Optionen c_cc[VTIME] und c_cc[VMIN].
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:
Für das Senden wird in der Regel die Funktion write() verwendet, deren erster Parameter der Filedeskriptor ist. Weitere Parameter sind die Adresse des Sendepuffers und die Anzahl der zu sendenden Bytes. Es muss auf jeden Fall überprüft werden, wieviele Bytes gesendet wurden (Rückgabewert von write()) und ob auch alle Bytes gesendet wurden.
int sendbytes(char * Buffer, int Count)
/* Sendet Count Bytes aus dem Puffer Buffer */
{
int sent; /* return-Wert */
/* Daten senden */
sent = write(fd, Buffer, Count);
if (sent < 0)
{
perror("sendbytes failed - error!");
return -1;
}
if (sent < Count)
{
perror("sendbytes failed - truncated!");
}
return sent;
}
Für das Empfangen wird in der Regel die Funktion read() verwendet, deren erster Parameter der Filedeskriptor ist. Weitere Parameter sind die Adresse des Sendepuffers und die maximale Anzahl der zu empfangenden Bytes. Die Funktion gibt die Anzahl der empfangenen Bytes zurück, wobei dieser Wert auch 0 sein kann. Das Verhalten von read() hängt von den Konfigurationswerten c_cc[VTIME] und c_cc[VMIN] ab. Bei der in open_serial() getroffenen Einstellung kehrt read() auf jeden Fall nach einer Sekunde zurück, ggf. ohne Zeichen empfangen zu haben. Dies ist bei der Programmierung zu berücksichtigen.
Das erste Programmfragment liest bis zu 100 Zeichen in einen Puffer:
char buf[101]; /* Eingabepuffer */
int anz; /* gelesene Zeichen */
...
anz = read(fd, (void*)buf, 100);
if (anz < 0)
perror("Read failed!");
else if (anz == 0)
perror("No data!");
else
{
buf[anz] = '\0'; /* Stringterminator */
printf("%i Bytes: %s", anz, buf);
}
...
Das Verfahren eignet sich insbesondere dann, wenn Sie wissen, wieviele Bytes
zu erwarten sind. Andernfalls gehen Sie vorsichtiger vor und lesen zeichenweise.
Diese Methode eignet sich auch gut, wenn auf ein bestimmtes empfangenes Byte
reagiert werden soll (Enter, Newline etc.):
char buf[101]; /* Eingabepuffer für die komplette Eingabe */
int anz; /* gelesene Zeichen */
char c; /* Eingabepuffer fuer 1 Byte */
int i; /* Zeichenposition bzw Index */
...
i = 0;
do /* Lesen bis zum Carriage Return, max. 100 Bytes */
{
anz = read(fd, (void*)&c, 1);
if (anz > 0)
{
if (c != '\r')
buf[i++] = c;
}
}
while (c != '\r' && i < 100 && anz >= 0);
if (anz < 0)
perror("Read failed!");
else if (i == 0) /* Nur zur Demo, dass auch mal nix kommt */
perror("No data!"); /* im Normalbetrieb loeschen! */
else
{
buf[i] = '\0'; /* Stringterminator */
printf("%i Bytes: %s", i, buf);
}
...
Sie sehen schon, das Empfangen wirft mehr Probleme auf, als das Senden.
Hier muss immer eine speziell an die Kommunikation angepasste Lösung
entwickelt werden.
Da der Raspberry Pi mit nur 3,3 V arbeitet, muss fast immer eine Pegelanpassung vorgenommen werden. Handelt es sich beim Kommunikationspartner ebenfalls um ein Mikrocontrollersystem, das aber mit 5 V arbeitet, ist die Anpassung relativ einfach. Der Sendeausgang TXD steuert einen Transistor an, der den Pegel auf 5 V anhebt. Da aber nun das Signal invertiert ist, wird ein zweiter Transiste dahintergeschaltet. Auf der Eingangsseite genügt ein Spannungsteiler, der das 5-V-Signal auf 3,3 V herunterteilt:
Sollen echte RS232-Pegel erzeugt werden, hilft ein entsprechender Konverterbaustein. Der MAX232 (Maxim) ist einer der ersten und beliebtesten TTL-zu-RS232-Konverter, nur arbeit er leider mit 5 V. Es gibt jedoch eine pinkompatible 3,3-V-Version, den MAX3232 (der sogar mit Spannungen zwischen 3 und 5,5 V arbeitet):
| JP1 | RasPi |
|---|---|
| 1 (Vcc) | 1 |
| 2 (TX) | 8 |
| 3 (RX) | 10 |
| 4 (GND) | 6 |
Für den Anschluss einer RS2r2-Schnittstelle wurde entsprechend dem Schaltplan oben eine Platine entwickelt, die mit SMD-Bauteilen bestückt wird. Der MAX3232 ist im SO16-Gehäuse lieferbar, das wie auch die Kondensatoren der Größe 1206 ohne Fummelei zu verlöten ist. Die Platine kann Dank der SMD-Technik einseitig bleiben, nur die Bestückungseite hat Kupferbahnen. Das folgende Bild zeigt den Bestückungsplan, wobei aus Gründen der Übersichtlichkeit die Masseflächen nicht gezeigt sind (bei EAGLE genügt der "ratsnest"-Befehl, um sie hervorzuzaubern).
Beachten Sie, dass der 9-polige Sub-D-Stecker auf der Unterseite der Platine sitzt und dessen Anschlusspins dann auf der Oberseite (Bestückungsseite) verlötet werden.
Die entsprechenden Dateien finden Sie hier:
EAGLE-Schaltplan
EAGLE-Board