Wie öffne, lese und schreibe ich von der seriellen Schnittstelle in C?

139

Ich bin ein bisschen verwirrt über das Lesen und Schreiben an eine serielle Schnittstelle. Ich habe ein USB-Gerät unter Linux, das den Konverter-Treiber für serielle FTDI-USB-Geräte verwendet. Wenn ich es anschließe, wird Folgendes erstellt: / dev / ttyUSB1.

Ich dachte, es wäre einfach, es in C zu öffnen und daraus zu lesen / schreiben. Ich kenne die Informationen zur Baudrate und Parität, aber es scheint, als gäbe es keinen Standard dafür?

Vermisse ich etwas oder kann mich jemand in die richtige Richtung weisen?

gnychis
quelle
18
Haben Sie sich das HOWTO zur seriellen Programmierung angesehen ?
Ribram
1
EDIT: Ich würde mir Ribrams Link ansehen. Es bleibt jedoch der Punkt, dass während ein serielles Gerät als Datei dargestellt wird, Geräte häufig spezifischere Schnittstellen haben, die über Systemaufrufe wie ioctlund implementiert werden fcntl.
Herr Shickadance
8
Aktualisierter Link zum Handbuch zur seriellen Programmierung für POSIX-Betriebssysteme .
svec
1
Grundlegendes zu den UNIX-Termios VMIN und VTIME ist eine hervorragende Ressource zum Verständnis von VTIME und VMIN, mit denen die Blockierungseigenschaften von read () an einer seriellen Schnittstelle behandelt werden.
Flak37
Verwenden Sie keinen Code aus Frerkings "Serial Programming HOWTO", wie im ersten Kommentar erwähnt. Sie sind nicht POSIX-konform geschrieben, daher sind die Codebeispiele nicht portierbar und funktionieren möglicherweise nicht zuverlässig für Sie.
Sägemehl

Antworten:

246

Ich habe das vor langer Zeit geschrieben ( von 1985 bis 1992, mit nur wenigen Änderungen seitdem ) und einfach die benötigten Teile kopiert und in jedes Projekt eingefügt.

Sie müssen cfmakeraweinen von ttyerhalten tcgetattr. Sie können nicht Null-out ein struct termios, konfigurieren Sie ihn, und legen Sie das ttymit tcsetattr. Wenn Sie die Zero-Out-Methode verwenden, treten unerklärliche intermittierende Fehler auf, insbesondere bei BSDs und OS X. "Unerklärte intermittierende Fehler" umfassen das Einhängen read(3).

#include <errno.h>
#include <fcntl.h> 
#include <string.h>
#include <termios.h>
#include <unistd.h>

int
set_interface_attribs (int fd, int speed, int parity)
{
        struct termios tty;
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tcgetattr", errno);
                return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // disable break processing
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
        {
                error_message ("error %d from tcsetattr", errno);
                return -1;
        }
        return 0;
}

void
set_blocking (int fd, int should_block)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tggetattr", errno);
                return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
                error_message ("error %d setting term attributes", errno);
}


...
char *portname = "/dev/ttyUSB1"
 ...
int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0)
{
        error_message ("error %d opening %s: %s", errno, portname, strerror (errno));
        return;
}

set_interface_attribs (fd, B115200, 0);  // set speed to 115,200 bps, 8n1 (no parity)
set_blocking (fd, 0);                // set no blocking

write (fd, "hello!\n", 7);           // send 7 character greeting

usleep ((7 + 25) * 100);             // sleep enough to transmit the 7 plus
                                     // receive 25:  approx 100 uS per char transmit
char buf [100];
int n = read (fd, buf, sizeof buf);  // read up to 100 characters if ready to read

Die Werte für die Geschwindigkeit sind B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, usw. Die Werte für die Parität sind 0(dh keine Parität), PARENB|PARODD(Parität ermöglichen und ungerade verwenden), PARENB(enable Parität und verwendet sogar), PARENB|PARODD|CMSPAR(mark Parität) und PARENB|CMSPAR( Raumparität).

"Blockieren" legt fest, ob ein read()am Port auf das Eintreffen der angegebenen Anzahl von Zeichen wartet. Wenn Sie keine Blockierung festlegen, wird read()zurückgegeben, wie viele Zeichen verfügbar sind, ohne auf weitere zu warten, bis zur Puffergrenze.


Nachtrag:

CMSPARwird nur für die Auswahl der Markierungs- und Leerzeichenparität benötigt, was ungewöhnlich ist. Für die meisten Anwendungen kann es weggelassen werden. Meine Header-Datei /usr/include/bits/termios.hkann CMSPARnur definiert werden, wenn das Präprozessorsymbol __USE_MISCdefiniert ist. Diese Definition erfolgt (in features.h) mit

#if defined _BSD_SOURCE || defined _SVID_SOURCE
 #define __USE_MISC     1
#endif

In den einleitenden Kommentaren von <features.h>heißt es:

/* These are defined by the user (or the compiler)
   to specify the desired environment:

...
   _BSD_SOURCE          ISO C, POSIX, and 4.3BSD things.
   _SVID_SOURCE         ISO C, POSIX, and SVID things.
...
 */
Wallyk
quelle
1
@wallyk: Auf meinem Computer gibt es keine Dateien mit dem Namen ttyUSB. Die einzigen Dateien mit dem Namen USB sind "usbmon". Der PC verfügt jedoch über viele USB-Anschlüsse. Wie konfiguriere ich sie?
Bas
3
@Bas: Wenn es sich um Linux handelt, verwenden Sie den Befehl lsusb, um alle USB-Geräte anzuzeigen . Sie können unterschiedlich benannt werden, wenn Ihr System benutzerdefinierte udevRegeln hat. siehe /etc/udev/rules.d/ Vielleicht von dort kann man den Port auswählen können Sie suchen. Sicherlich können Sie den Unterschied feststellen, indem Sie den Port auflisten und dann entfernen / anschließen.
Wallyk
1
@ wallyk Ich kann keine Ausgabe (nicht in der Lage zu schreiben) mit Space Parity (PARENB | CMSPRAR) erhalten. Aber ich kann mit mark Parity kommunizieren. Irgendwelche Ideen, wie man es löst?
Bas
5
Für eine Kritik dieses Codes siehe stackoverflow.com/questions/25996171/…
Sägemehl
2
Wie in Ich habe Daten an ein ttyUSB0-Gerät gesendet und sie kamen von meinem tty-Gerät, das ich tatsächlich verwendet habe. Ich habe buchstäblich mein eigenes Terminal mit diesem Code gespammt. Die folgende Antwort von Sägemehl ist eine sicherere Implementierung.
Eule
50

Für Demo-Code, der dem POSIX-Standard entspricht, wie im Handbuch Festlegen der Terminalmodi und der seriellen Programmierung für POSIX-Betriebssysteme beschrieben , wird Folgendes angeboten.
Es ist im Wesentlichen von der anderen Antwort abgeleitet, aber ungenaue und irreführende Kommentare wurden korrigiert.

#include <errno.h>
#include <fcntl.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    /* setup for non-canonical mode */
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    /* fetch bytes as they become available */
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 1;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

void set_mincount(int fd, int mcount)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error tcgetattr: %s\n", strerror(errno));
        return;
    }

    tty.c_cc[VMIN] = mcount ? 1 : 0;
    tty.c_cc[VTIME] = 5;        /* half second timer */

    if (tcsetattr(fd, TCSANOW, &tty) < 0)
        printf("Error tcsetattr: %s\n", strerror(errno));
}


int main()
{
    char *portname = "/dev/ttyUSB0";
    int fd;
    int wlen;

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    }
    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);
    //set_mincount(fd, 0);                /* set to pure timed read */

    /* simple output */
    wlen = write(fd, "Hello!\n", 7);
    if (wlen != 7) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd);    /* delay for output */


    /* simple noncanonical input */
    do {
        unsigned char buf[80];
        int rdlen;

        rdlen = read(fd, buf, sizeof(buf) - 1);
        if (rdlen > 0) {
#ifdef DISPLAY_STRING
            buf[rdlen] = 0;
            printf("Read %d: \"%s\"\n", rdlen, buf);
#else /* display hex */
            unsigned char   *p;
            printf("Read %d:", rdlen);
            for (p = buf; rdlen-- > 0; p++)
                printf(" 0x%x", *p);
            printf("\n");
#endif
        } else if (rdlen < 0) {
            printf("Error from read: %d: %s\n", rdlen, strerror(errno));
        } else {  /* rdlen == 0 */
            printf("Timeout from read\n");
        }               
        /* repeat read to get full message */
    } while (1);
}

Damit das Programm die empfangenen Daten als ASCII-Codes behandelt, kompilieren Sie das Programm mit dem Symbol DISPLAY_STRING, z

 cc -DDISPLAY_STRING demo.c

Wenn es sich bei den empfangenen Daten um ASCII-Text (und nicht um Binärdaten) handelt und Sie diese als durch das Zeilenumbruchzeichen abgeschlossene Zeilen lesen möchten, finden Sie in dieser Antwort ein Beispielprogramm.

Sägespäne
quelle
1
Vieles davon könnte durch genau das cfmakerawRichtige ersetzt werden?
CMCDragonkai
Andere Beispiele, die ich gesehen habe, öffnen den Port auch mit O_NDELAYoder O_NONBLOCK. In cmrr.umn.edu/~strupp/serial.html wird erwähnt, dass beim Öffnen des Dateideskriptors mit diesen Flags das VTIMEignoriert wird. Was ist dann der Unterschied zwischen dem Ausführen mit dem O_NONBLOCKDateideskriptor und dem Ausführen mit VTIME?
CMCDragonkai
@CMCDragonkai - Es ist viel komplizierter als das, was Sie geschrieben haben. Siehe stackoverflow.com/questions/25996171/…, in dem auf die akzeptierte Antwort auf diese Frage verwiesen wird. Übrigens, selbst wenn Sie das Terminal im nicht blockierenden Modus öffnen, können Sie mit einem fcntl ()
Sägemehl am
Entschuldigung für die Neulingsfrage, aber wo beenden Sie die do while-Schleife in main oder wird sie für immer wiederholt?
Bakalolo
1
@bakalolo - Es ist nur ein einfacher Demo-Code, der für immer empfangen und angezeigt werden kann. Die Absicht ist portabler Code, der kompiliert wird (ohne Fehler) und zuverlässig funktioniert (im Gegensatz zur anderen Antwort). Ein Test zum Bestimmen des Nachrichtenendes könnte hinzugefügt werden. Bei Rohdaten hängt die Definition eines Nachrichtenpakets vom Protokoll ab. Oder dieser Code könnte geändert werden, um nur die empfangenen Daten in einem Ringpuffer zu speichern, damit ein anderer Thread sie verarbeiten kann, wie in dieser Antwort beschrieben .
Sägemehl