wake-up-neo.com

Lesen und Schreiben an der seriellen Schnittstelle in C unter Linux

Ich versuche Senden/Empfangen von Daten über einen USB-Port mit FTDI, daher muss ich die serielle Kommunikation mit C/C++ abwickeln. 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, dass read() 0 zurückgibt (überhaupt keine gelesenen Bytes) oder blockiert bis zum Timeout (VTIME). Ich gehe davon aus, dass dies passiert, weil write() nichts sendet. In diesem Fall empfängt das Gerät keinen Befehl und ich kann keine Antwort erhalten. Das Ausschalten des Geräts, während das Lesen meines Programms blockiert ist, hat tatsächlich zu einer Antwort geführt (das Gerät sendet beim Herunterfahren etwas).

Seltsam ist, dass man das hinzufügt

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

direkt nach dem Aufruf von write() erhalte ich:

I've written 6 bytes

das ist genau das, was ich erwarte. Nur mein Programm funktioniert nicht so, wie es sollte, wie mein Gerät kann nicht empfangen, was ich gerade schreibe am Port.

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

Kann mir jemand sagen, wo ich falsch liege?

Danke im Voraus.

EDIT: Zuvor verwendete Version dieses Codes

unsigned char cmd[] = "INIT \n"

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

<command><SPACE><CR>.

Ich habe auch versucht, das O_NONBLOCK - Flag beim Lesen zu umgehen, aber dann blockiere ich nur bis für immer. Ich habe versucht, select() zu verwenden, aber nichts passiert. Nur zum Ausprobieren habe ich eine Warteschleife erstellt, bis Daten verfügbar sind, aber mein Code verlässt die Schleife nie. Übrigens ist Warten oder usleep() etwas, das ich vermeiden muss. Gemeldete ist nur ein Auszug aus meinem Code. Vollständiger Code muss in einer Echtzeitumgebung funktionieren (speziell OROCOS), damit ich nicht wirklich eine schlafähnliche Funktion möchte.

40
Lunatic999

Ich habe meine Probleme gelöst und poste hier den richtigen Code für den Fall, dass jemand ähnliches benötigt.

offener Port

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 definitiv nicht notwendig, Byte für Byte zu schreiben, auch int n_written = write( USB, cmd, sizeof(cmd) -1) funktionierte einwandfrei.

Endlich read:

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 für mich gearbeitet. Danke euch allen!

66
Lunatic999

1) Ich würde nach init eine/n hinzufügen.write (USB, "init\n", 5);

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

3) Höchstwahrscheinlich: Addiere ein"usleep (100000);nach demwrite (). Der Dateideskriptor ist so eingestellt, dass er nicht blockiert oder wartet. Wie lange dauert es, bis Sie eine Antwort zurückbekommen, bevor Sie read aufrufen können? (It muss vom Kernel durch Systemhardware-Interrupts empfangen und gepuffert werden, bevor Sieread ()it.) Haben Sie darüber nachgedacht,zu verwenden?/select ()um auf etwas zu wartenread ()? Vielleicht mit einem Timeout?

Bearbeitet zum Hinzufü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;
0
guest

Einige Empfänger erwarten eine EOL-Sequenz, die normalerweise aus zwei Zeichen besteht \r\n. Versuchen Sie also, 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 erforderlich, jedes Zeichen in Anführungszeichen zu setzen.

0
radarhead