Wie kann ich von der seriellen Schnittstelle in C öffnen, lesen und schreiben?

Lesezeit: 10 Minuten

Benutzeravatar von gnychis
Gnychis

Ich bin ein wenig verwirrt über das Lesen und Schreiben an einer seriellen Schnittstelle. Ich habe ein USB-Gerät unter Linux, das den Konvertertreiber für serielle USB-Geräte von FTDI verwendet. Wenn ich es anschließe, erstellt es: /dev/ttyUSB1.

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

Übersehe ich etwas, oder kann mich jemand in die richtige Richtung weisen?

  • Hast du dir mal die angeschaut Serielle Programmierung HOWTO?

    – Ribram

    4. August 2011 um 19:30 Uhr

  • EDIT: Ich würde mir den Link von Ribram ansehen. Der Punkt bleibt jedoch, dass ein serielles Gerät zwar als Datei dargestellt wird, Geräte jedoch häufig über spezifischere Schnittstellen verfügen, die über Systemaufrufe wie implementiert werden ioctl und fcntl.

    – Herr Schickadance

    4. August 2011 um 19:31 Uhr


  • Aktualisierter Link zu Serielles Programmierhandbuch für POSIX-Betriebssysteme.

    – svek

    28. Dezember 2013 um 1:29 Uhr


  • Verstehen der UNIX-Begriffe VMIN und VTIME ist eine großartige Ressource, um VTIME und VMIN zu verstehen, die verwendet werden, um die Blockierungseigenschaften eines read() an einer seriellen Schnittstelle zu handhaben.

    – flak37

    11. August 2014 um 20:43 Uhr


  • Verwenden Sie keinen Code aus Frerkings “Serial Programming HOWTO”, wie im ersten Kommentar erwähnt. Sie sind nicht POSIX-kompatibel geschrieben, daher sind die Codebeispiele nicht portierbar und funktionieren möglicherweise nicht zuverlässig für Sie.

    – Sägespäne

    22. Januar 2019 um 0:37 Uhr


wallyks Benutzeravatar
Wallyk

Ich habe dies vor langer Zeit geschrieben (aus den Jahren 1985-1992, mit nur wenigen Änderungen seitdem), und kopiere einfach die benötigten Bits und füge sie in jedes Projekt ein.

Sie müssen anrufen cfmakeraw auf einen tty erhalten von tcgetattr. Sie können a nicht auf Null setzen struct termioskonfigurieren Sie es und legen Sie dann die tty mit tcsetattr. Wenn Sie die Zero-Out-Methode verwenden, werden Sie unerklärliche zeitweilige Fehler erleben, insbesondere auf den BSDs und OS X. „Unerklärliche zeitweilige Fehler“ beinhalten Hängenbleiben 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 Geschwindigkeit sind B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800usw. Die Werte für die Parität sind 0 (bedeutet keine Parität), PARENB|PARODD (Parität aktivieren und ungerade verwenden), PARENB (Parität aktivieren und gerade verwenden), PARENB|PARODD|CMSPAR (Markenparität) und PARENB|CMSPAR (Raumparität).

„Blockieren“ legt fest, ob a read() am Port wartet auf das Eintreffen der angegebenen Anzahl von Zeichen. Einstellung keine Sperrung bedeutet, dass a read() gibt jedoch viele Zeichen zurück, ohne auf weitere zu warten, bis zur Puffergrenze.


Nachtrag:

CMSPAR wird 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.h ermöglicht die Definition von CMSPAR nur wenn das Präprozessorsymbol __USE_MISC ist definiert. Diese Definition kommt vor (in features.h) mit

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

Die einleitenden Bemerkungen von <features.h> sagt:

/* 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: Auf meinem Computer gibt es keine Dateien mit dem Namen ttyUSB, die einzigen Dateien mit dem Namen USB sind “usbmon”. Aber der PC hat viele USB-Anschlüsse. Wie konfiguriere ich sie also?

    – Bas

    23. April 2014 um 6:18 Uhr

  • @Bas: Wenn es Linux ist, verwenden Sie den Befehl lsusb um alle USB-Geräte anzuzeigen. Sie könnten anders benannt werden, wenn Ihr System benutzerdefinierte ist udev Regeln; sehen /etc/udev/rules.d/ Vielleicht können Sie dort den gesuchten Hafen heraussuchen. Sicherlich können Sie den Unterschied erkennen, indem Sie den Port auflisten und dann aus-/einstecken.

    – Wallyk

    23. April 2014 um 13:48 Uhr

  • @ wallyk Ich kann keine Ausgabe (nicht schreiben) mit Leerzeichenparität (PARENB | CMSPRAR) erhalten. Aber ich kann mit Mark Parity kommunizieren. Irgendwelche Ideen, wie man es löst?

    – Bas

    5. Mai 2014 um 5:11 Uhr

  • Eine Kritik dieses Codes finden Sie unter stackoverflow.com/questions/25996171/…

    – Sägespäne

    17. Februar 2015 um 9:00 Uhr

  • Wie in habe ich Daten an ein ttyUSB0-Gerät gesendet und es kam aus meinem tty-Gerät, das ich tatsächlich benutzte. Ich habe mit diesem Code buchstäblich mein eigenes Terminal zugespammt. Die Antwort unten von Sägemehl ist eine sicherere Implementierung.

    – Eule

    1. Oktober 2018 um 13:42 Uhr


der Benutzeravatar von sawdust
Sägespäne

Für Democode, der dem POSIX-Standard entspricht, wie in beschrieben Richtige Einstellung der Terminalmodi
und Serielles Programmierhandbuch für POSIX-Betriebssystemewird folgendes angeboten.
Dieser Code sollte mit Linux auf x86- sowie ARM- (oder sogar CRIS-) Prozessoren korrekt ausgeführt werden.
Es ist im Wesentlichen von der anderen Antwort abgeleitet, aber ungenaue und irreführende Kommentare wurden korrigiert.

Dieses Demoprogramm öffnet und initialisiert ein serielles Terminal mit 115200 Baud für den nicht-kanonischen Modus, das so portabel wie möglich ist.
Das Programm überträgt eine hartcodierte Textzeichenfolge an das andere Terminal und verzögert, während die Ausgabe durchgeführt wird.
Das Programm tritt dann in eine Endlosschleife ein, um Daten vom seriellen Terminal zu empfangen und anzuzeigen.
Standardmäßig werden die empfangenen Daten als hexadezimale Bytewerte angezeigt.

Damit das Programm die empfangenen Daten als ASCII-Code 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 (anstelle von Binärdaten) handelt und Sie sie als Zeilen lesen möchten, die durch das Zeilenumbruchzeichen abgeschlossen werden, finden Sie in dieser Antwort ein Beispielprogramm.


#define TERMINAL    "/dev/ttyUSB0"

#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 = TERMINAL;
    int fd;
    int wlen;
    char *xstr = "Hello!\n";
    int xlen = strlen(xstr);

    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, xstr, xlen);
    if (wlen != xlen) {
        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);
}

Ein Beispiel für ein effizientes Programm, das das Puffern empfangener Daten bereitstellt und dennoch eine Byte-für-Byte-Übergabe der Eingabe ermöglicht, finden Sie in dieser Antwort.


  • Vieles davon könnte einfach ersetzt werden cfmakeraw Rechts?

    – CMCDragonkai

    20. Januar 2017 um 12:28 Uhr

  • Andere Beispiele, die ich gesehen habe, öffnen den Port auch mit O_NDELAY oder O_NONBLOCK. Das cmrr.umn.edu/~strupp/serial.html erwähnt, dass, wenn Sie den Dateideskriptor mit diesen Flags öffnen, dann die VTIME wird ignoriert. Was ist dann der Unterschied zwischen Laufen mit O_NONBLOCK Dateideskriptor im Vergleich dazu VTIME?

    – CMCDragonkai

    20. Januar 2017 um 12:55 Uhr

  • @CMCDragonkai – Es ist viel komplizierter als das, was Sie geschrieben haben. Siehe stackoverflow.com/questions/25996171/…, das auf die akzeptierte Antwort auf diese Frage verweist. Übrigens, auch wenn Sie das Terminal im nicht blockierenden Modus öffnen, können Sie immer noch mit a in den blockierenden Modus zurückkehren fcntl()

    – Sägespäne

    20. Januar 2017 um 23:57 Uhr

  • @bakalolo — Es ist nur ein einfacher Democode, der für immer empfangen und angezeigt werden kann. Die Absicht ist portabler Code, der kompiliert (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 die empfangenen Daten nur in einem Ringpuffer zu speichern, damit ein anderer Thread sie verarbeiten kann, wie in dieser Antwort beschrieben.

    – Sägespäne

    9. Februar 2017 um 2:37 Uhr

  • @dstromberg – die “Basic” Terminal, das termios unterstützt, ist ein Hardcopy-Terminal im Teletype-Stil und kein VDT ​​wie VT220, was nicht der Fall ist “Basic” in jedem Sinne. Lesen Sie noch einmal die Mann Seiten und der Code. “Code ist die Dokumentation.” “Alles, was es ist, …” — Schlechte Grammatik und eine mehrdeutige Referenz machen überhaupt keinen Sinn.

    – Sägespäne

    4. August 2020 um 0:42 Uhr


1424780cookie-checkWie kann ich von der seriellen Schnittstelle in C öffnen, lesen und schreiben?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy