![]() |
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. Für das Verständnis der seriellen Schnittstelle ist es wichtig zu wissen, dass es je eine Datenleitung für das Senden und Empfangen der Daten (TD/RD), zwei Paare von Handshake-Leitungen RTS und CTS bzw. DTR und DSR, zwei Statusleitungen CD und RI sowie GND (Massepotenzial) gibt. Vom Computer aus gesehen sind die Leitungen TD, RTS, DTR Ausgänge, die Leitungen RD, CTS, DSR, CD, RI Eingänge. RTS und DTR können also programmgesteuert aktiviert und deaktiviert werden, CTS, DSR, CD und RI können nur gelesen werden. Die folgende Grafik zeigt die übliche Belegung der Schnittstelle beim 9-poligen RS-232-Stecker.

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. Damit läßt sich die Schnittstelle aber nur komplett aktivieren oder deaktivieren (nach dem Deaktivieren ist /dev/ttyAMA0 dann nicht mehr vorhanden und steht auch fürdie Programmierung nicht mehr zur Verfügung.
Fehlt also bei Ihrem System die Datei /etc/inittab, kann die Deaktivierung des Login-Terminals auf der seriellen Schnittstelle über systemctl mit den beiden folgenden Befehlen vorgenommen werden. Die Schnittstelle bleibt dann für die Programmierung aktiv:
sudo systemctl stop serial-getty@ttyAMA0.service sudo systemctl disable serial-getty@ttyAMA0.serviceBeim Raspberry Pi, Model 3, existieren zwei serielle Schnittstellen (siehe folgenden Abschnitt), weshalb bei diesem Board die Befehle geändert werden müssen:
sudo systemctl stop serial-getty@ttyS0.service sudo systemctl disable serial-getty@ttyS0.serviceMit der aktuellen Raspbian-Version müssen Sie sich nicht mehr mit den beiden Dateien herumschlagen. Im Konfigurationstool raspi-config gibt es einen Menüpunkt zum Ein- und Ausschalten der seriellen Konsole. Sie rufen raspi-config auf und wählen dann die Menüoptionen "Advanced Options" → "Serial" → "Nein" (bzw. "No" in der englischen Sprachvariante) zum Deaktivieren der seriellen Konsole.
Das BCM2837 auf dem Raspberry Pi3 hat 2 UARTs (wie auch seine Vorgänger), jedoch zur Unterstützung Die Bluetooth-4.2-Funktionalität des voll ausgestatteten PL011-UART wurde von den Header-Pins auf den Bluetooth-Chip verschoben und der Mini-UART auf den Header-Pins 8 und 10 verfügbar gemacht. 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 (siehe unten). Insgesamt hat dies eine Reihe von Konsequenzen für Benutzer der seriellen Schnittstelle. Zum einen muss beim Abschalten der seriellen Konsole ttyS0 anstelle von ttyAMA0 angegeben werden (siehe oben) und zum anderen sind weitere Anpssungen notwendig.
Das Device /dev/ttyAMA0, das zuvor für den Zugriff auf den UART verwendet wurde, stellt jetzt eine Verbindung mit Bluetooth her. Der miniUART ist jetzt auf /dev/ttyS0 verfügbar. In der neuesten Betriebssystem-Software (ab "Jessie") gibt es ein Symlink /dev/serial0, das beim RasPi 3 auf /dev/ttyS0 verweist und bei den anderen Modellen auf /dev/ttyAMA0. Beim RasPi 3 gibt es dann noch /dev/serial1 als Verweis auf /dev/ttyAMA0. So kann man sich beim Programmieren mittels /dev/serial0 auf die serielle Schnittstelle am GPIO beziehen und das Programm läuft auf allen Modellen ohne Anpassung.
Der Mini Uart hat folgende Eigenschaften:
core_freq=250Dies behebt das Timing-Problem und scheint wenig Einfluss auf andere Dinge zu haben. Die SPI-Taktfrequenz und ARM Timer sind ebenfalls abhängig vom System Clock.
Aus irgendeinem seltsamen Grund ist ab dem Kernel 4.4.9 "DISABLE UART" die Standard-Voreinstellung - vermutlich, um Konflikte mit Bluetooth zu verhindern. Um die serielle Schnittstelle zu aktivieren, müssen Sie enable_uart=1 in der Datei /boot/config.txt setzen. Dies behebt angeblich auch die core_freq-Problematik, so dass der obige Eintrag ggf. nicht mehr notwendig ist. Wer sicher gehen will, schreibt auf jeden Fall beides in die Datei /boot/config.txt:
core_freq=250 enable_uart=1Wenn Sie Bluetooth nicht verwenden, ist es möglich, die beiden seriellen Ports im Devicetree zu vertauschen oder Bluetooth mittels Devicetree-Overlay ganz abzuschalten. Zum Vertauchen fügen Sie am Ende der Datei /boot/config.txt die Zeile
dtoverlay=pi3-miniuart-bthinzu. Nach einem Neustart funktioniert die UART-Zuordnung wieder wie in den Vorgängerversionen. Mittels pi3-disable-bt kann man Bluetooth komplett abschalten. Dann muss aber auch noch das bluetooth-Modem deaktiviert werden, was über systemctl mit dem folgenden Kommando erreicht wird:
sudo systemctl disable hciuart
Zum Testen, ob die serielle Schnittstelle auch wunschgemäß arbeitet, verbinden Sie die GPIO-Ports TX und RX des UART miteinander (Pins 8 und 10 der Stiftleiste). Dmit wird jedes gesendete Byte gleich wieder auf den Empfang zurückgegeben. Dann starten Sie das Teminalprogramm Minicom (ggf. muss das erst nachinstalliert werden) im lokalen Modus, d. h. ohne Senden von Init-Strings etc.:
minicom -b 9600 -o -D /dev/ttyAMA0Jedes Zeichen, das Sie eintippen wird nun als Echo auf dem Bildschirm angezeigt (sofern alles in Ordnung ist). Einen Zeilenvorschub erreichen Sie übrigens mit "Strg-J" (Linefeed). Beenden läßt sich Minicom mit der Tastenfolge "Strg-A" und "q".
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.
Für das Ansprechen der seriellen Schnittstelle mit Python wird das Modul pySerial benötigt. Die Installation erfolgt mit der Paketverwaltung ganz einfach:
sudo apt-get -y install python-serialFür Python, Version3, ersetzen Sie "python-serial" durch "python3-serial". Die Dokumentation von pySerial findet man auf https://github.com/pyserial/pyserial. Dort gibt es unter "pySerial API" eine vollständige Beschreibung der Klassen. Einige davon sind allerdings plattformspezifisch (Linux, Windows). Mit folgendem Programm werden auf der Tastatur eingegebene Zeichen an ein externes Device gesendet und von dort empfangene Zeichen auf der Konsole ausgegeben. Zum Test kann man beispielsweise zwei RasPis über ein Kabel mit gekreuzten Verbindungen zwischen RXD (Receive Data) und TXD (Transmit Data) miteinander verbinden und auf beiden das Programm laufen lassen. Bei nur einm Raspberry Pi kann man auch nur diese beiden Pins miteinander verbinden. Dann werden alle gesendeten Zeichen sofort wieder empfangen.
Beim Öffnen der seriellen Schnittstelle kann man, wie auch bei C, die Einstellungen vornehmen. Diese Einstellungen lassen sich aber auch nachträglich festlegen oder ändern. Eine typische Initialisierung könnte folgendermaßen aussehen:
ser = serial.Serial(
port="/dev/ttyS0",
baudrate = 9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=30
)
Die Standard-Voreinstellung ist 8 Datenbist, 1 Stoppbit, keine Parität - diese Angaben
dürfen daher auch fehlen. Obligatorisch sind die Angabe von Port und Baudrate.
Zum Lesen der empfangenen Zeichen muss man eine nicht-blockierende Funktion verwenden,
wenn das Programm auch laufend prüfen muss, ob eine Taste gedrückt wurde etc. Die Methode
ser.read() blockiert nicht, wenn im Konstruktor der timeout-Parameter auf 0
gesetzt wird. Mit der Methode ser.isopen() kann man feststellen, ob der
Zugriff auf die Schnittstelle geklappt hat. Die Methoden serialPort.flushInput()
und serialPort.flushOutput() dienen zum Löschen von Empfangs- und Sendepuffer.
Einige Eigenschaften für den Handshake kann man bei Linux-PCs setzen, bei denen die Steuerleitungen der seriellen Schnittstelle vorhanden sind. Dazu dienen folgende Variablen:
ser.setRTS(val) ser.setDTR(val) ser.setBreak(val) val = ser.getCTS() val = ser.getDSR() val = ser.getRI()Beim Raspberry Pi muss man ggf. die Steuerleitungen über GPIO-Pins realisieren.
Zum Senden von Bytes/Zeichen wird die Methode write() verwendet, zum Beispiel:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from serial import Serial
ser = Serial("/dev/ttyUSB0", baudrate=9600, timeout=1)
for i in range(10):
ser.write(b"Hello World\n")
ser.close()
In Python 3 werden unterschiedliche Typen für Text (string) und binäre Daten (bytes) benutzt.
Bei der seriellen Schnittstelle muss man binäre Daten versenden. Der Text muss also in einen
Binärstring umgewandelt werden. Dies kann entweder mit einem vorangestellten 'b' geschehen
(z. B.: ser.write(b"Hello World\n")) oder mit der Methode encode() (z. B.:
ser.write("Hello World\n".encode())). Als Parameter kann bei encode()
die Kodierung angegeben werden (z. BV. utf-8, utf-16, latin-1). Ohne Parameter wird die
Default-Codierung des Betriebssystems verwendet. Bei Python 2 ist die Umcodierung nicht
nötig.
Zum Einlesen von Daten kann man eine der Methoden read(n), readline(nmax) oder readlines() verwenden. In Klammern wird die Anzahl der zu lesenden Bytes bzw. die maximale Anzahl der zu lesenden Bytes angegeben. Wird das Timeout erreicht, owohl noch nicht die angegebene Anzahl von Zeichen gelesen wurde, so bricht die Funktion ab. Invers zu encode() wandelt die Methode decode() den Bytestring wieder in einen Textstring zurück. Mittels inWaiting() kann man ermitteln, wieviele Zeichen noch im Puffer warten. Bei den folgenden Beispielen wurden am USB-Seriell-Adapter einfach die Pins TXD und RXD gebrückt. Das erste Programm schickt einfach die eingegebenen Zeichen zurück. #!/usr/bin/env python # -*- coding: utf-8 -*- import serial ser = serial.Serial("/dev/ttyUSB0", baudrate=19200, timeout=0.1) if (ser.isOpen() == True): ser.close() ser.open() try: while True: ch = ser.read() ser.write(ch) # Keyboard Interrupt abfangen, zum beenden mit [STRG]+[C]. except(KeyboardInterrupt, SystemExit): print("Abbruch") ser.close()
Beim nächsten Programm wird der ASCII-Zeichensatz ausgegeben und wieder eingelesen.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import serial
import time
outStr = ''
inStr = ''
ser = serial.Serial("/dev/ttyUSB0", 19200, timeout=2)
if (ser.isOpen() == True):
ser.close()
ser.open()
for i, a in enumerate(range(33, 126)):
outStr += chr(a)
ser.write(outStr)
time.sleep(0.05)
inStr = ser.read(ser.inWaiting())
print "inStr = " + inStr
print "outStr = " + outStr
if(inStr == outStr):
print "Bingo!"
else:
print "Ooops!"
ser.close()
Im folgenden Beispiel kommunizieren zwei Programme über die serielle Schnittstelle. Für den Test kann man beide Programme in verschiedenen Shell-Fenstern laufen lassen. sender.py sendet einen String ("Zählerstand: nn"), wobei der Zähler dauern inkrementiert wird. receive.py empfängt den String und gibt ihn am Bildschirm aus. Zuerst das Sende-Programm sender.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import serial
counter = 0
ser = serial.Serial(port='/dev/ttyUSB0', baudrate = 19200, timeout = 1)
if (ser.isOpen() == True):
ser.close()
ser.open()
try:
while 1:
ser.write('Zählerstand: %d \n'%(counter))
time.sleep(1)
counter += 1
# Keyboard Interrupt abfangen, zum beenden mit [STRG]+[C].
except(KeyboardInterrupt, SystemExit):
print("Abbruch")
ser.close()
Und nun das Empfangsprogramm receive.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import serial
ser = serial.Serial(port='/dev/ttyUSB0', baudrate = 19200, timeout=1)
if (ser.isOpen() == True):
ser.close()
ser.open()
try:
while 1:
x = ser.readline().rstrip() # LF am Ende entfernen
print x
# Keyboard Interrupt abfangen, zum beenden mit [STRG]+[C].
except(KeyboardInterrupt, SystemExit):
print("Abbruch")
ser.close()
Interessant ist die serielle Kommunikation auch, wenn man den Raspberry Pi mit einem Arduino verbinden will. Der Arduino hängt dann per USB einerseits an der seriellen Schnittstelle und wird andererseits über die USB-Buchse mit Energie versorgt. Je nach Modell wird die Schnittstelle als /dev/ttyAMC0 (Arduino UNO) oder als /dev/ttyUSB0 (Arduino NANO) identifiziert. Hier kann eine automatische Ermittlung des Ports nach folgendem Muster helfen (die sich auch auf weitere serielle Komponenten erweitern liesse):
...
# Arduino NANO detektieren
cmd = "dmesg | grep ttyUSB | grep FTDI | head -1 | sed -e 's/.* //'"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
ArduinoNano = str.strip(process.stdout.read())
if ArduinoNano:
PORT = '/dev/tty'+ArduinoNano
else:
# Arduino UNO detektieren
cmd = "dmesg | grep ttyACM | head -1 | sed -e 's/.*tty//' -e 's/:.*//'"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
ArduinoUno = str.strip(process.stdout.read())
if ArduinoUno:
PORT = '/dev/tty'+ArduinoUno
else:
exit(1)
...
Die Verbindung zum Arduino wird nun mit dem folgenden Befehl aufgebaut, egal welcher
Arduino angestöpselt ist.
ser = serial.Serial(port=PORT, baudrate = 9600, timeout=30)
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 TX des Raspberry mit ca. 2,8 V reicht manchmal schon aus, um den 5-V-Controller anzusteuern. Jedoch liegt diese Ausgangsspannung im Grenzbereich - mal klappte es mal nicht. Die Schaltung im Bild oben dient dazu, das Digitalsignal des Raspberry Pi auf 5 V zu verstärken. Wenn am Eingang mehr als etwa 1,5 V anliegen, sperrt der Transistor, der in BAsisschaltung betrieben wird. Am Ausgang liegt über den 1-Kiloohm-Widerstand eine Spannung von 5 V an. Führt der Eingang Masse-Potential, ist der Transistor durchgeschaltet und der Ausgang liegt ebenfalls auf nahezu Masse-Potential. Auf der Eingangsseite genügt für die Wandlung 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):
Zum Betrieb sind nur noch fünf Kondensatoren zu je 100 nF nötig und schon werden die 3,3-V-Pegel in ±12 V umgesetzt. Da keine Steuerleitungen verwendet werden, benötigen Sie nur jeweils einen der beiden Sende- oder Empfangskreise.
Die Anschlüsse des Raspberry Pi, TXD und RXD werden mit der "TTL/CMOS"-Seite des IC verbunden und eine 9-polige Sub-D-Buchse mit der RS232-Seite. Je nach Beschaltung der Gegenseite müssen eventuell die Pins 2 und 3 der Sub-D-Buchse vertauscht werden (Hier schlägt der "Fluch der seriellen Schnittstelle" zu). Die Zuordnung von JP1 und RasPi-Steckerleiste ist:
| JP1 | RasPi |
|---|---|
| 1 (Vcc) | 1 |
| 2 (TX) | 8 |
| 3 (RX) | 10 |
| 4 (GND) | 6 |
Für den Anschluss einer RS232-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. Achtung: Vom MAX3232 gibt es zwei Bauformen unterschiedlicher Breite - bitte beim Einkauf beachten.
Die entsprechenden Dateien finden Sie unten bei den Links.