Lesen und Schreiben auf die serielle Schnittstelle in C unter Linux

Lesezeit: 9 Minuten

Benutzeravatar von Lunatic999
Verrückt999

Ich versuche zu Senden/Empfangen von Daten über einen USB-Port mit FTDI, also muss ich die serielle Kommunikation mit C/C++ handhaben. Ich arbeite an Linux (Ubuntu).

Grundsätzlich bin ich mit einem Gerät verbunden, das auf eingehende Befehle wartet. Ich muss diese Befehle senden und die Antwort des Geräts lesen. Sowohl Befehle als auch Antwort sind ASCII-Zeichen.

Mit GtkTerm funktioniert alles einwandfrei, aber wenn ich zur C-Programmierung wechsle, treten Probleme auf.

Hier ist mein Code:

#include <stdio.h>      // standard input / output functions
#include <stdlib.h>
#include <string.h>     // string function definitions
#include <unistd.h>     // UNIX standard function definitions
#include <fcntl.h>      // File control definitions
#include <errno.h>      // Error number definitions
#include <termios.h>    // POSIX terminal control definitions

/* Open File Descriptor */
int USB = open( "/dev/ttyUSB0", O_RDWR| O_NONBLOCK | O_NDELAY );

/* Error Handling */
if ( USB < 0 )
{
cout << "Error " << errno << " opening " << "/dev/ttyUSB0" << ": " << strerror (errno) << endl;
}

/* *** Configure Port *** */
struct termios tty;
memset (&tty, 0, sizeof tty);

/* Error Handling */
if ( tcgetattr ( USB, &tty ) != 0 )
{
cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << endl;
}

/* Set Baud Rate */
cfsetospeed (&tty, B9600);
cfsetispeed (&tty, B9600);

/* Setting other Port Stuff */
tty.c_cflag     &=  ~PARENB;        // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;
tty.c_cflag     &=  ~CRTSCTS;       // no flow control
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_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines
tty.c_iflag     &=  ~(IXON | IXOFF | IXANY);// turn off s/w flow ctrl
tty.c_lflag     &=  ~(ICANON | ECHO | ECHOE | ISIG); // make raw
tty.c_oflag     &=  ~OPOST;              // make raw

/* Flush Port, then applies attributes */
tcflush( USB, TCIFLUSH );

if ( tcsetattr ( USB, TCSANOW, &tty ) != 0)
{
cout << "Error " << errno << " from tcsetattr" << endl;
}

/* *** WRITE *** */

unsigned char cmd[] = {'I', 'N', 'I', 'T', ' ', '\r', '\0'};
int n_written = write( USB, cmd, sizeof(cmd) -1 );

/* Allocate memory for read buffer */
char buf [256];
memset (&buf, '\0', sizeof buf);

/* *** READ *** */
int n = read( USB, &buf , sizeof buf );

/* Error Handling */
if (n < 0)
{
     cout << "Error reading: " << strerror(errno) << endl;
}

/* Print what I read... */
cout << "Read: " << buf << endl;

close(USB);

Was passiert, ist das read() gibt 0 zurück (überhaupt keine Bytes gelesen) oder blockiert bis zum Timeout (VTIME). Ich gehe davon aus, dass dies geschieht, weil write() sendet nichts. In diesem Fall würde das Gerät keinen Befehl empfangen und ich kann keine Antwort erhalten. Tatsächlich war es beim Ausschalten des Geräts, während mein Programm beim Lesen blockiert war, tatsächlich erfolgreich, eine Antwort zu erhalten (das Gerät sendet etwas, während es heruntergefahren wird).

Seltsam ist, dass dies hinzugefügt wird

cout << "I've written: " << n_written << "bytes" << endl; 

gleich nach write() Anruf, ich erhalte:

I've written 6 bytes

das ist genau das, was ich erwarte. Nur mein Programm funktioniert nicht so wie es soll, wie z Mein Gerät kann nicht empfangen, was ich schreibe am Hafen.

Ich habe verschiedene Dinge und Lösungen ausprobiert, auch in Bezug auf Datentypen (ich habe versucht, std::string zu verwenden, z cmd = "INIT \r" oder const char), aber nichts hat wirklich funktioniert.

Kann mir jemand sagen wo ich falsch liege?

Danke im Voraus.

BEARBEITEN:
Frühere Version dieses Codes verwendet

unsigned char cmd[] = "INIT \n"

und auch cmd[] = "INIT \r\n". Ich habe es geändert, weil Befehlssyntax für mein Gerät als gemeldet wird

<command><SPACE><CR>.

Ich habe auch versucht, das zu vermeiden O_NONBLOCK Flag beim Lesen, aber dann blockiere ich nur bis für immer. Ich habe versucht, mit select() aber nichts passiert. Nur für einen Versuch habe ich eine Warteschleife erstellt, bis Daten verfügbar sind, aber mein Code verlässt die Schleife nie. Btw, abwarten bzw usleep() ist etwas, das ich vermeiden muss. Gemeldet ist nur ein Auszug meines Codes. Vollständiger Code muss in einer Echtzeitumgebung funktionieren (insbesondere OROCOS), also möchte ich keine schlafähnliche Funktion.

  • std::cout ist C++. Im Allgemeinen sollte sich Ihr MCVE an eine Sprache halten, es sei denn, Sie vermuten eine schlechte Interaktion. Andernfalls beginnen einige Leute zu argumentieren, dass C++ nicht C ist.

    – jww

    12. Mai 2019 um 22:56 Uhr

Benutzeravatar von Lunatic999
Verrückt999

Ich habe meine Probleme gelöst, also poste ich hier den richtigen Code, falls jemand ähnliches braucht.

Hafen öffnen

int USB = open( "/dev/ttyUSB0", O_RDWR| O_NOCTTY );

Parameter einstellen

struct termios tty;
struct termios tty_old;
memset (&tty, 0, sizeof tty);

/* Error Handling */
if ( tcgetattr ( USB, &tty ) != 0 ) {
   std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
}

/* Save old tty parameters */
tty_old = tty;

/* Set Baud Rate */
cfsetospeed (&tty, (speed_t)B9600);
cfsetispeed (&tty, (speed_t)B9600);

/* Setting other Port Stuff */
tty.c_cflag     &=  ~PARENB;            // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;

tty.c_cflag     &=  ~CRTSCTS;           // no flow control
tty.c_cc[VMIN]   =  1;                  // read doesn't block
tty.c_cc[VTIME]  =  5;                  // 0.5 seconds read timeout
tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines

/* Make raw */
cfmakeraw(&tty);

/* Flush Port, then applies attributes */
tcflush( USB, TCIFLUSH );
if ( tcsetattr ( USB, TCSANOW, &tty ) != 0) {
   std::cout << "Error " << errno << " from tcsetattr" << std::endl;
}

Schreiben

unsigned char cmd[] = "INIT \r";
int n_written = 0,
    spot = 0;

do {
    n_written = write( USB, &cmd[spot], 1 );
    spot += n_written;
} while (cmd[spot-1] != '\r' && n_written > 0);

Es war auch definitiv nicht notwendig, Byte für Byte zu schreiben int n_written = write( USB, cmd, sizeof(cmd) -1) funktionierte gut.

Zu guter Letzt, lesen:

int n = 0,
    spot = 0;
char buf="\0";

/* Whole response*/
char response[1024];
memset(response, '\0', sizeof response);

do {
    n = read( USB, &buf, 1 );
    sprintf( &response[spot], "%c", buf );
    spot += n;
} while( buf != '\r' && n > 0);

if (n < 0) {
    std::cout << "Error reading: " << strerror(errno) << std::endl;
}
else if (n == 0) {
    std::cout << "Read nothing!" << std::endl;
}
else {
    std::cout << "Response: " << response << std::endl;
}

Dieser hat bei mir funktioniert. Danke euch allen!

  • Ihre Linie response.append(&buf); kompiliert nicht für mich. Auch nicht while(buf != '\r' && n > 0);. Sind das Tippfehler oder übersehe ich etwas?

    – josaphatv

    30. Juni 2014 um 22:14 Uhr

  • Der Code, den Sie zum Senden vorschlagen int n_written = write( USB, cmd, sizeof(cmd) -1) funktioniert nicht wirklich für große Puffer. In diesem Fall benötigen Sie eine Schleife wie die oben gezeigte, aber mit n_written = write( USB, cmd + spot, command_length - spot). Außerdem müssten Sie die Länge verwenden, um die Schleife zu beenden, anstatt jedes Zeichen einzeln zu überprüfen.

    – Fritz

    9. April 2015 um 14:06 Uhr


  • cfmakeraw() setzt bereits eine Reihe von Attributen. Diese Zeilen brauchst du nicht: tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; tty.c_cflag &= ~PARENB;

    – rumpeln

    22. Oktober 2017 um 6:02 Uhr


  • Warum die Besetzung von (speed_t) in cfsetospeed (&tty, (speed_t)B9600);? Im schlimmsten Fall verstummt es eine nützliche Warnung, dass die verwendete Konstante nicht in a passt speed_t. Im besten Fall ist es unnötig.

    – chux – Wiedereinsetzung von Monica

    24. Juni 2020 um 15:33 Uhr


Einige Empfänger erwarten eine EOL-Sequenz, die normalerweise aus zwei Zeichen besteht \r\nalso versuchen Sie in Ihrem Code, die Zeile zu ersetzen

unsigned char cmd[] = {'I', 'N', 'I', 'T', ' ', '\r', '\0'};

mit

unsigned char cmd[] = "INIT\r\n";

Übrigens ist der obige Weg wahrscheinlich effizienter. Es ist nicht nötig, jedes Zeichen zu zitieren.

  • Außerdem haben einige USB-Laufwerke einen schnellen Schreibcache, was bedeutet, dass sie in einem Puffer sitzen, bevor sie tatsächlich geschrieben werden, richtig?

    – Magn3s1um

    7. August 2013 um 16:47 Uhr

  • Danke für den Tipp, aber \r\n ist nicht mein Problem. Ich habe es ausprobiert und nichts ändert sich wirklich. Andere Ideen?

    – Verrückt999

    8. August 2013 um 10:05 Uhr

  • @Lunatic999 Ich habe gerade deinen Code und die Zeile noch einmal gelesen int n_written = write( USB, cmd, sizeof(cmd) -1 ); stört mich irgendwie. Wären Sie bereit, die Verwendung zu testen? int n_written = write(USB, cmd, strlen(cmd)); ? Das habe ich in einem Programm, an dem ich gearbeitet habe, ein sehr ähnliches Setup wie Ihres, Kommunikation über ein ttyUSB.

    – Radarkopf

    8. August 2013 um 16:32 Uhr

Benutzeravatar des Gastes
Gast

1) Ich würde ein /n nach init hinzufügen. dh schreiben (USB, “init\n”, 5);

2) Überprüfen Sie die Konfiguration der seriellen Schnittstelle. Die Chancen stehen gut, dass da etwas nicht stimmt. Nur weil Sie ^Q/^S oder Hardware-Flusskontrolle nicht verwenden, heißt das nicht, dass die andere Seite dies nicht erwartet.

3) Höchstwahrscheinlich: Füge hinzu ein “schlafen (100000); nach dem schreiben(). Der Dateideskriptor ist so eingestellt, dass er nicht blockiert oder wartet, richtig? Wie lange dauert es, bis Sie eine Antwort erhalten, bevor Sie „Lesen“ aufrufen können? (Es muss vom Kernel über System-Hardware-Interrupts empfangen und gepuffert werden, bevor Sie dies können lesen() it.) Haben Sie darüber nachgedacht, zu verwenden auswählen() auf etwas warten lesen()? Vielleicht mit Timeout?

Bearbeitet, um hinzuzufügen:

Benötigen Sie die DTR/RTS-Leitungen? Hardware-Flusskontrolle, die die andere Seite anweist, die Computerdaten zu senden? z.B

int tmp, serialLines;

cout << "Dropping Reading DTR and RTS\n";
ioctl ( readFd, TIOCMGET, & serialLines );
serialLines &= ~TIOCM_DTR;
serialLines &= ~TIOCM_RTS;
ioctl ( readFd, TIOCMSET, & serialLines );
usleep(100000);
ioctl ( readFd, TIOCMGET, & tmp );
cout << "Reading DTR status: " << (tmp & TIOCM_DTR) << endl;
sleep (2);

cout << "Setting Reading DTR and RTS\n";
serialLines |= TIOCM_DTR;
serialLines |= TIOCM_RTS;
ioctl ( readFd, TIOCMSET, & serialLines );
ioctl ( readFd, TIOCMGET, & tmp );
cout << "Reading DTR status: " << (tmp & TIOCM_DTR) << endl;

  • Vielen Dank für Ihre Antwort! Ich habe versucht, was du vorschlägst, aber nichts geändert. Ich habe meine Frage mit mehr Informationen bearbeitet … Irgendwelche anderen Ideen? Danke nochmal

    – Verrückt999

    8. August 2013 um 9:51 Uhr

  • usleep() dient nur zum Debuggen/Testen. Sobald es funktioniert, können Sie zu select() wechseln, um nur die erforderliche Zeit auf eine Antwort zu warten.

    – Gast

    8. August 2013 um 19:15 Uhr

  • Wie hast du 7 Bytes geschrieben? Es ist “INIT \r”, was 6 Bytes ist … Schreiben Sie das nachgestellte NULL-Zeichen? Manche Systeme interpretieren ein NULL-Zeichen als Kommunikationsende…

    – Gast

    8. August 2013 um 19:21 Uhr

  • Entschuldigung, 7 Bytes waren mein Fehler beim Melden. Ich schreibe definitiv die richtige Anzahl von Bytes (6). 7 bezog sich auf einen Versuch mit \r\n Ende. Ich werde meine Frage bearbeiten. Jedenfalls habe ich alles repariert und jetzt funktioniert es. Ich werde den endgültigen Code posten, falls jemand etwas Ähnliches brauchen sollte. Wirklich danke für eure Antworten!

    – Verrückt999

    8. August 2013 um 19:37 Uhr

1405280cookie-checkLesen und Schreiben auf die serielle Schnittstelle in C unter Linux

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

Privacy policy