Linux C Lectura/Escritura del Puerto Serie


Estoy tratando de enviar/recibir datos a través de un puerto USB usando FTDI, por lo que necesito manejar la comunicación en serie usando C/C++. Estoy trabajando en Linux (Ubuntu).

Básicamente, estoy conectado a un dispositivo que está escuchando los comandos entrantes. Necesito enviar esos comandos y leer la respuesta del dispositivo. Tanto los comandos como la respuesta son caracteres ASCII.

Todo funciona bien usando GtkTerm pero, cuando cambio a la programación en C, me encuentro problema.

Aquí está mi código:

#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);

Lo que sucede es que read() devuelve 0 (sin bytes leídos en absoluto) o bloquea hasta el tiempo de espera (VTIME). Asumo que esto sucede porque write() no envía nada. En ese caso, el dispositivo no recibiría el comando y no puedo recibir respuesta. De hecho, apagar el dispositivo mientras mi programa está bloqueado en la lectura en realidad tuvo éxito en obtener una respuesta (dispositivo envía algo mientras se apaga).

Lo extraño es que añadiendo esto

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

Justo después de write() llamar, recibo:

I've written 6 bytes

Que es exactamente lo que espero. Solo que mi programa no funciona como debería, como mi dispositivo no puede recibir lo que realmente estoy escribiendo en el puerto.

He probado diferentes cosas y soluciones, también con respecto a los tipos de datos (he intentado usar std::string, como cmd = "INIT \r" o const char), pero nada funcionó realmente.

¿Puede alguien decirme dónde me equivoco?

Gracias en avance.

EDITAR: Versión anterior de este código utilizado

unsigned char cmd[] = "INIT \n"

Y también cmd[] = "INIT \r\n". Lo cambié porque el comando sintax para mi dispositivo se informa como

<command><SPACE><CR>.

También he intentado evitar la bandera O_NONBLOCK al leer, pero luego solo bloqueo hasta para siempre. He intentado usar select() pero no pasa nada. Solo para intentarlo, he creado un bucle de espera hasta que los datos estén disponibles, pero mi código nunca sale del bucle. Por cierto, esperar o usleep() es algo que necesito evitar. Uno reportado es solo un extracto de mi código. El código completo debe funcionar en un entorno en tiempo real (específicamente OROCOS), por lo que realmente no quiero una función similar al sueño.

Author: dsolimano, 2013-08-07

3 answers

He resuelto mis problemas, así que publico aquí el código correcto en caso de que alguien necesite cosas similares.

Puerto abierto

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

Establecer parámetros

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

Escribe

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

Definitivamente no era necesario escribir byte por byte, también int n_written = write( USB, cmd, sizeof(cmd) -1) funcionó bien.

Por fin, léase :

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

Este funcionó para mí. ¡Gracias a todos!

 56
Author: Lunatic999,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-24 04:54:07

Algunos receptores esperan una secuencia EOL, que normalmente es de dos caracteres \r\n, así que intente en su código reemplazar la línea

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

Con

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

POR cierto, la forma anterior es probablemente más eficiente. No hay necesidad de citar cada personaje.

 0
Author: radarhead,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-07 16:43:29

1) Añadiría a / n después de init. es decir, write (USB, "init\n", 5);

2) Compruebe la configuración del puerto serie. Las probabilidades son que algo está mal allí. Solo porque no uses ^Q / ^S o control de flujo de hardware no significa que el otro lado no lo esté esperando.

3) Lo más probable: Añade un "usleep(100000); después del write(). El descriptor de archivo está configurado para no bloquear o esperar, ¿verdad? Cuánto tiempo se tarda en obtener una respuesta antes de llamar a leer? (Tiene que ser recibido y almacenado en búfer por el núcleo, a través de interrupciones de hardware del sistema, antes de que pueda leerlo().) ¿ Has considerado usar select () para esperar algo para read () ? ¿Tal vez con un tiempo muerto?

Editado para Añadir:

¿Necesita las líneas DTR/RTS? Control de flujo de hardware que le dice al otro lado que envíe los datos de la computadora? por ejemplo,

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
Author: guest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-08 19:29:10