2012-05-09 18 views
13

Estoy leyendo bytes desde un puerto serie en C++ usando un descriptor de archivo y la función posix/unix read(). En este ejemplo, estoy leyendo 1 byte desde el puerto serie (ajustes de velocidad de transmisión y similar se han omitido para mayor claridad):¿Cómo puedo implementar el tiempo de espera para leer() al leer desde un puerto serie (C/C++)

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

int main(void) 
{ 
    int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); 
    char buf[1]; 
    int bytesRead = read(fd, buf, 1); 
    close(fd); 
    return 0; 
} 

Si el dispositivo conectado a/dev/ttyS0 no envía ninguna información, el programa colgar. ¿Cómo puedo establecer un tiempo de espera?

He intentado fijar un tiempo de espera de esta manera:

struct termios options; 
tcgetattr(fd, &options); 
options.c_cc[VMIN] = 0; 
options.c_cc[VTIME] = 10; 
tcsetattr(fd, TCSANOW, &options); 

pensé que iba a dar 1 segundo tiempo, pero no hace ninguna diferencia. Creo que he entendido mal a VMIN y VTIME. ¿Para qué se utilizan VMIN y VTIME?

Luego busqué en la web y encontré a alguien hablando de la función select(). ¿Es esa la solución y, de ser así, cómo se podría aplicar eso al programa anterior para hacer un tiempo de espera de 1 segundo?

Cualquier ayuda es apreciada. Gracias de antemano :-)

+0

Uso de 'tcsetattr()' 'de VTIME' no es sencilla; requiere otras configuraciones de modo que algunos controladores serie no son compatibles. Ver mi respuesta para una solución general. – wallyk

+0

Esta es la mejor explicación que he encontrado en la web para VMIN y VTIME [http://unixwiz.net/techtips/termios-vmin-vtime.html](http://unixwiz.net/techtips/termios- vmin-vtime.html). Según el artículo, cuando el bit ICANON está desactivado, habilita un "modo en bruto" que cambia la interpretación de VMIN y VTIME. Configurar el bit ICANON haría que el código funcionara como se esperaba. – arpl

Respuesta

16

Sí, utilice select(2). Pase un conjunto de descriptores de archivo que contenga solo su fd en el conjunto de lectura y los conjuntos de escritura/excepción vacíos, y pase un tiempo de espera apropiado. Por ejemplo:

int fd = open(...); 

// Initialize file descriptor sets 
fd_set read_fds, write_fds, except_fds; 
FD_ZERO(&read_fds); 
FD_ZERO(&write_fds); 
FD_ZERO(&except_fds); 
FD_SET(fd, &read_fds); 

// Set timeout to 1.0 seconds 
struct timeval timeout; 
timeout.tv_sec = 1; 
timeout.tv_usec = 0; 

// Wait for input to become ready or until the time out; the first parameter is 
// 1 more than the largest file descriptor in any of the sets 
if (select(fd + 1, &read_fds, &write_fds, &except_fds, &timeout) == 1) 
{ 
    // fd is ready for reading 
} 
else 
{ 
    // timeout or error 
} 
+0

Muchas gracias. Eso parece funcionar como yo quería. Tengo tres preguntas más: ¿Puedo usar Seleccionar para hacer un tiempo de espera cuando abro un puerto también? ¿Puedo usar el mismo código para un tiempo de espera en la operación de escritura, configurando FD_SET (fd, & write_fds)? Y último: ¿para qué se usa except_fds? – pvh1987

+2

encuesta() es generalmente mejor que select(). –

+0

¡Funciona muy bien! Si ejecuto esto en un bucle, ¿necesito FD_ZERO y FD_SET en cada iteración, o una vez antes de que el ciclo esté bien? – Andy

0

Existen varios enfoques posibles. Si el programa eventualmente sincronizará más de una operación de E/S, select() es la opción más clara.

Sin embargo, si la única entrada es de esta E/S, la selección de E/S sin bloqueo y la sincronización es un método sencillo. He ampliado desde un solo carácter de E/S de múltiples caracteres para que sea un ejemplo más general completa:

#include <fcntl.h> 
#include <termios.h> 
#include <unistd.h> 
#include <sys/time.h> 

int main(void) 
{ 
    int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); // sometimes "O_NONBLOCK" 
    char buf[10]; 
    int done = 0, inbuf = 0; 
    struct timeval start, now; 

    gettimeofday (&start, NULL); 
    while (!done) 
    { 
     int bytesRead = read(fd, &buf[inbuf], sizeof buf - inbuf); 
     if (bytesRead < 0) 
     { 
      error_processing_here(); 
      continue; 
     } 
     if (bytesRead == 0) // no data read to read 
     { 
      gettimeofday (&now, NULL); 
      if ((now.tv.sec - start.tv_sec) * 1000000 + 
       now.tv.usec - start.tv_usec > timeout_value_in_microsecs) 
      { 
       done = 2; // timeout 
       continue; 
      } 
      sleep(1); // not timed out yet, sleep a second 
      continue; 
     } 
     inbuf += bytesRead; 
     if (we have read all we want) 
      done = 1; 
    } 
    if (done == 2) 
     timeout_condition_handling(); 
    close(fd); 
    return 0; 
} 
+0

Este código es mucho más que usar select(), y puede esperar hasta un segundo después de que los datos estén disponibles antes de ser leídos. –

+0

@ MartinC.Martin: Bueno, puede parecer así porque mi código es un ejemplo de trabajo completo que incluye la lectura y la comprobación de errores, mientras que el ejemplo de selección muestra solo un fragmento de lo que se necesitaría. – wallyk

+0

Puede haber una avalancha de datos durante 1 segundo de inactividad (especialmente a altas velocidades de transmisión), por lo que no es una buena idea utilizar el modo de suspensión. Un sueño más corto también podría desperdiciar el tiempo del procesador, lo cual es más importante en dispositivos de baja potencia. Este código podría mejorarse tomando un apporach que no sea de sondeo usando 'select'. – Isaac

1

Puede intentar la señal de captura para detener la operación de lectura. utilizar la alarma (1) antes de lectura, y si no se volvió función de lectura, alarma enviará la señal SIGALRM, a continuación, puede crear la función procesamiento de señal para captar esta señal, así:

#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <setjmp.h> 

static jmp_buf env_alarm; 

static void sig_alarm(int signo) 
{ 
    longjmp(env_alarm, 1); 
} 

int main(void) 
{ 
    int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); 
    char buf[1]; 

    if (signal(SIGALRM, sig_alarm) == SIG_ERR) 
    { 
     exit(0); 
    } 

    if (setjmp(env_alarm) != 0) 
    { 
     close(fd); 
     printf("Timeout Or Error\n"); 
     exit(0); 
    } 

    alarm(1); 
    int bytesRead = read(fd, buf, 1); 
    alarm(0); 

    close(fd); 
    return 0; 
} 

Pero usar select o poll o epoll será mejor si tu programa es grande.

+0

Hay un caso interesante sobre seleccionar, comprueba que fd puede escribirse, pero será bloque cuando escriba, por lo que creo que el método de selección no puede funcionar bien en mi caso, pero su método puede funcionar bien. es un buen método – kangear

1

¿Para qué se utilizan VMIN y VTIME?

Si MIN> 0 y TIEMPO = 0, MIN. Establece el número de caracteres para recibir antes de que la lectura sea satisfecha. Como TIME es cero, el temporizador no se usa.

Si MIN = 0 y TIME> 0, TIME sirve como valor de tiempo de espera. La lectura será cumplida si se lee un solo carácter o se excede el TIEMPO (t = TIEMPO * 0.1 s). Si se excede el TIEMPO, no se devolverá ningún carácter.

Si MIN> 0 y HORA> 0, TIME sirve como un temporizador entre caracteres.La lectura se cumplirá si se reciben caracteres MIN, o el tiempo entre dos caracteres excede el TIEMPO. El temporizador se reinicia cada vez que se recibe un carácter y solo se activa después de que se haya recibido el primer carácter .

Si MIN = 0 y TIEMPO = 0, la lectura se cumplirá inmediatamente. Se devolverá el número de caracteres actualmente disponibles o el número de caracteres solicitado. De acuerdo con Antonino (ver contribuciones), , podría emitir un fcntl (fd, F_SETFL, FNDELAY); antes de leer para obtener el mismo resultado.

Fuente: http://tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html

Cuestiones relacionadas