Raspberry Pi: Serielle Schnittstelle

Prof. Jürgen Plate

Raspberry Pi: Serielle Schnittstelle

Allgemeines

Bei einem seriellen, asynchronen Datentransfer werden die einzelnen Bits eines Datenbytes nacheinander über eine Leitung übertragen (siehe 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.

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 115200

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 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.

Serielle Schnittstelle freischalten

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:

Bearbeiten /boot/cmdline.txt

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 rootwait
Aus 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 rootwait
in 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

Bearbeiten /etc/inittab

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 vt100
Damit ist die Login-Funktion abgeschaltet. Wenn Sie den Pi nun neu starten, können Sie die serielle Schnittstelle beliebig nutzen.

Änderungen beim Raspberry Pi Modell 3

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. Diese beiden Pins sind nun mit dem Mini-UART verbunden, der an den Core-Takt des Videocore IV gebunden ist. 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.

Programmierung der seriellen Schnittstelle in C

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, B38400
Beim PC kamen dann noch hinzu (Modem bzw. maximale Geschwindigkeit der seriellen PC-Schnittstelle):
 
B57600, B115200
Mit 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, B4000000
Es 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/ttyAMA0
Die Ä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.

Die im folgenden gezeigte Schnittstellenprogrammierung funktioniert analog natürlich auch bei USB-seriellen Schnittstellen (/dev/ttyUSB0 etc.), es muss nur beim Öffnen der Schnittstelle das entsprechende Device angegeben werden.

Öffnen der Schnittstelle

Die folgende C-Funktion stellt eine allgemein verwendbare Routine für das Öffnen der Schnittstelle dar. Da es zahllose Optionen gibt (siehe Headerdateien bzw. obige Dokumentnation), werden hier die notwendigsten Optionen so gesetzt, dass sie die häufigsten Praxisfälle abdecken. Auf zwei besondere Optionen, options.c_cc[VMIN] und options.c_cc[VTIME], wird weiter unten noch genauer eingegangen. Das Datenformat wird auf 8 Datenbist, 1 Stoppbit, keine Parität gesetzt. In der Praxis kommen eigentlich nur drei Formate vor:
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:


Wenn das Programm nicht ewig auf eine Eingabe warten soll, nimmt man also am besten den dritten Fall.

Bytes senden

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;
  }

Bytes empfangen

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.

Pegelanpassung der seriellen Schnittstelle

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):

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:
JP1RasPi
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


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate
Letzte Aktualisierung: